on c19bc7a help sign in
686 lines 21 kB history raw
1:- :~ navhome/'/docs/'
2 sort/'4'
3 next/'true'
4 title/'Sail'
5 ==
6;>
8# Introduction to Sail
10Sail is Hoon markup that’s used to render a web page with XML. But what makes
11it special is that you can run arbitrary Hoon code within such a web page
12without using a separate markup language. In this tutorial, we will
13be dealing with Sail as it renders to HTML, a subset of XML.
15### Getting Started
17Before starting, make sure that your urbit is
18[mounted to Unix](https://urbit.org/docs/using/setup/)
20To host that output, your ship also has a web-server, called
21[Eyre](https://urbit.org/docs/using/web/), that can be found at
22`http://localhost:8080/` if it’s your first ship that’s running on the a
23machine, `http://localhost:8081/` if it’s the second ship on that same machine,
24and so on. In the startup messages, a ship will tell you which HTTP port it's
25using.
27The `/web` directory of the `%home` desk is served by default. You can change
28which desk you're serving by using the command `|serve` in the Dojo. If you
29have already created and mounted a desk named `%sandbox`, for example, you can
30switch to serving it with this command:
32```
33|serve %sandbox
34```
36The first time you serve a desk, `/web` will be included as default. After that,
37you will need to explicitly append `/web` to the desk you're switching to --
38otherwise, you'll need to write `http://localhost:8080/web` every time you
39navigate to your web-server. Newly served desks not automatically having `/web`
40as their root directory is a bug.
42To switch back to your `%home` desk and set `/web` as your root directory, run:
44```
45|serve %/web
46```
48We'll be serving the `%home` desk for the examples in this tutorial.
50#### Rendering
52That server has access to source files located in `/<your-urbit>/home/web` and
53any sub-folders thereof. For the purposes of this tutorial, we will only be
54using files that are located in the `/<your-urbit>/home/web/pages` path.
56There is an important distinction between these two paths. According to
57`/ren/urb.hoon`, any file inside of `/web` or any of its sub-directories
58is put through a tree of renderers specified at `/ren/urb/tree.hoon`
59-- _except_ for files inside of `/web/pages`. All of the files in `/web` and
60its non-`/pages` children directories have various operations on files, such as
61wrapping your Sail the `html`, `head`, and `body` nodes. This rendering tool,
62called simply *Tree*, also adds certain formatting polish, such as a sidebar
63that lists sibling files in the same directory.
65The `/web/pages` sub-directory is a unique case with its own rendering rules.
66The Tree is not used when rendering files in this path. The existence of no tag
67is inferred, and no special formatting is applied. Such simplicity is ideal for
68introducing the basics of Sail. Any source file located in `/web/pages` must
69produce a complete HTML node with "head" and "body" nodes as children.
71Understanding the details of the rendering pipeline is not necessary to complete
72this tutorial. But, if you are curious, you might find it illuminating to
73explore the contents of the `/ren` directory.
75So, if a Sail source file named `sailtest.hoon` is saved in your `/web/pages`
76folder on your only running urbit process, you can view that source rendered
77into a web-page at `localhost:8080/pages/sailtest/`.
79### The Basics
81It’s easy to see how Sail can directly translate to HTML:
83#### Sail code
85```
86;html
87 ;head
88 ;title: My first webpage
89 ==
90 ;body
91 ;h1: Welcome!
92 ; Hello, world!
93 ; We're on the web.
94 ;div;
95 ;script@"http://unsafely.tracking.you/cookiemonster.js";
96 ==
97==
98```
100#### HTML code
102```
103<html>
104 <head>
105 <title>My first webpage</title>
106 </head>
107 <body>
108 <h1>Welcome!</h1>
109 Hello, world!
110 We’re on the web.
111 <div></div>
112 <script src="http://unsafely.tracking.you/cookiemonster.js"></script>
113 </body>
114</html>
115```
117Save the above Sail code in `/home/web/pages/firstsail.hoon`, and the above
118HTML code in `/home/web/pages/firsth.html`. Access the resulting pages by
119navigating to `http://localhost:8080/pages/firstsail` and
120`http://localhost:8080/pages/firsth.html`, respectively, in your browser. The
121resulting pages should be identical!
123It shouldn't be hard to see the similarities here. So let's go into more
124detail about what the differences are.
126### Tags and Closing
128In Sail, tag heads are written with the tag name prepended by `;`. Unlike in
129HTML, there are different ways on closing tags, depending on the needs of the
130tag. One of the nice things about Hoon is that you don’t have to be constantly
131closing expressions; Sail inherits this convenience.
133#### Empty
135Empty tags are closed with a `;` following the tag. Example:
137```
138;div;
139```
141Equals:
143```
144<div></div>
145```
147#### Filled
149Filled tags are closed via line-break. To fill text inside, add `:` after the
150tag name, then insert your plain text following a space. Example:
152```
153;h1: The title
154```
156Equals:
158```
159<h1>The title</h1>
160```
162#### Nested
164To nest tags, simply create a new line. Nested tags need to be closed
165with `==`, because they expect a list of sub-tags. If we nest a line of plain
166text with no tag, a `<p>` is prepended.
168Sail, like Hoon, is white-space sensitive. Unlike in Hoon, however, two spaces
169(called a "gap") is not necessarily equivalent to a line-break in Sail.
171```
172;body
173 ;h1: Blog title
174 This is some good content.
175==
176```
178Equals:
180```
181<body>
182 <h1>Blog title</h1>
183 <p>This is some good content.</p>
184</body>
185```
188Conversely, if we want to write a string with no tag at all, then we can prepend
189those untagged lines with `;`.
191```
192;body
193 ;h1: Welcome!
194 ; Hello, world!
195 ; We’re on the web.
196==
197```
199Equals:
201```
202<body>
203 <h1>Welcome~</h1>
204 Hello, world.
205 We’re on the web.
206</body>
207```
210#### Attributes
212Attributes are key-value pairs that go into an HTML node.
214Adding attributes is simple: just add the desired attribute between parentheses,
215right after the tag name without a space. We separate different attributes of
216the same node by using `,`.
218Attributes can be used in two forms: flat, which uses one line; and tall, which
219uses multiple lines, when a single line would not be practical. Flat forms and
220flat forms are two syntaxes of semantically equivalent expressions.
223#### Generic
225The code below produces a “Submit” button on the page.
227Wide-form Sail:
229```
230;input(type "submit", value "Submit");
231```
233Tall-form Sail:
235```
236;input
237 =type "submit"
238 =value "Submit";
239==
240```
242Equals:
244```
245<input type="submit" value="Submit">`
246```
248#### IDs
250```
251;nav#header: Menu
252```
254Equals:
256```
257<nav id="header">Menu</nav>
258```
260Add `#` after tag name to add an ID.
262#### Classes
264`;h1.text-blue: Title` equals `<h1 class="text-blue">Title</h1>`
266Add `.` after tag name to add a class. However, if you want a class name that
267contains a space, you will need to use use the syntax of a generic attribute:
269`;div(class "logo inverse");` equals `<div class="logo inverse"></div>`
272#### Image
274`;img@"example.png";`
276Equals
278`<img src="example.png"/>`
280Add `@` after the tag name to link your source.
282To add attributes to the image, like size specifications, add the
283desired attribute after the `"` of the image name and before the final `;` of
284the `img` tag.
286For example:
288`;img@"https://urbit.org/example.png"(width "100%");`
290#### Linking
292`;a/"urbit.org": A link to Urbit.org`
294Equals
296`<a href="urbit.org">A link to Urbit.org</a>`
298Add `/` after tag name to start an href.
300### Hoon in Sail
302Our first example is useful to understand how Sail syntax translates to HTML
303syntax, but Sail finds its usefulness in the fact that it can be used seamlessly
304with Hoon code.
306One place where Sail diverges from HTML is that it supports string
307interpolation. In Sail, the contents of all tags are a string type called `tape`
308unless otherwise designated. Tapes are delineated by the double quotes `""`.
309That’s important, because tapes can be interpolated with Hoon expressions,
310provided that those expressions produce something a tape as well. Interpolation
311is done by wrapping the code to be inserted in `{}`. In your Dojo prompt, try
312the command below (without the `>`):
314```
315> "I am a l{(mul 3 11)}t Urbit user."
316! exit
317```
319Oh no! That command produced a nest-fail error, which indicates that there was
320a problem related to Hoon type-system. We used the proper `{}` syntax for
321interpolation, and we used the proper function-call syntax inside of it. So
322what happened?
324Trying the `mul` function on its own will help answer this question. Try the
325following command in your Dojo:
327```
328> (mul 3 11)
32933
330```
332This should work, producing `33`, an atom. That’s our problem. The
333interpolation expression expects a tape, but our first command tried to
334interpolate with something that results in an atom. Try this now:
336```
337> <(mul 3 11)>
338"33"
339```
341We get a similar result, but we know that it’s a tape because it’s wrapped in
342`""`. Bingo! With this knowledge, let’s perform a successful interpolation:
344```
345> "I am a l{<(mul 3 11)>}t Urbit user."
346"I am a l33t Urbit user."
347```
349Great!
351### The Subject
353The default subject of a Sail file is different from the subject that
354expressions in the Dojo or a Hoon generator are evaluated against.
356To see this difference, first save the code below to a file at
357`home/web/pages/sub.hoon` and view the rendering at
358`http://localhost:8080/pages/sub`.
360```
361;html
362 ;head;
363 ;body
364 {<.>}
365 ==
366==
367```
369Then enter the following command in the Dojo:
371```
372> .
373```
375You'll notice that the Sail subject is like a stripped-down version of the Dojo
376subject. Its final line should match the Dojo subject.
378Now swap the code below into your Sail file.
380```
381/= gas /$ fuel:html
382;html
383 ;head;
384 ;body
385 {<.>}
386 ==
387==
388```
390You'll find that the resulting Sail subject is much larger. If you look closely,
391you'll see that there's some new similarities with the Dojo subject, such as
392containing the name of your ship. There's also some new differences, since
393`fuel:html` contains things that are specifically useful for performing
394web-related operations.
396### A More Interesting Example
398Now let’s apply knowledge of string interpolation and the Sail subject in a Sail
399source file. The following code produces more interesting things than the
400example we saw before.
402```
403/= gas /$ fuel:html
404=/ show-list &
405=+ wid=(met 3 p.bem.gas)
406=/ what-kind
407?: (lte wid 1) "galaxy"
408?: =(2 wid) "star"
409?: (lte wid 4) "planet"
410?: (lte wid 8) "moon"
411 "comet"
412;html
413 ;head
414 ;meta(charset "utf-8");
415 ;title: A more advanced page
416 ==
417 ;body
418 ;h1: This is the Title
419 ; This is a
420 ;a/"http://urbit.org": link to Urbit.org.
421 ;span: Check it out!
422 ;+ ?: show-list
423 ;ol
424 ;li(style "color: green"): We're doing interesting stuff now.
425 ;li: We're pretty-printing this sum with Hoon: {<(add 50 50)>}.
426 ;li: The code above is shorthand for {(scow %ud (add 50 50))}.
427 ;li: I am {(trip '~lodleb-ritrul')}.
428 ;li: Actually, my name is {<p.bem.gas>}. I'm a {what-kind}.
429 ==
430 ;div: My name is {<p.bem.gas>}. I'm a {what-kind}.
431 ==
432==
433```
435Save the above Sail code in to `home/web/pages/secondsail.hoon` and access the
436resulting page by navigating to `http://localhost:8080/pages/secondsail` in
437your browser.
439There are some interesting things here. Let’s go through this code piece by
440piece.
442```
443/= gas /$ fuel:html
444```
446Later in this code we want produce the name of the ship is hosting it, something
447that looks like ~lodleb-ritrul. In a generator, we could produce our ship name
448by writing `our` -- try it in the Dojo. But remember that Sail is rendered
449against its own subject, `our` is not part of the default subject. To augment
450our subject with the information that we need, we use `/= gas /$ fuel:html`.
452The above expression uses two Ford runes, `/=` and `/$`, to add the relevant
453information to the current subject. We can pull the ship’s name out of this
454augmented subject later. It's important to note that these runes are not part of
455Hoon. They are part of Ford, our build system.
457`/$` is used to get data from the environment, and `/=` adds that data to
458subject in the face `gas`. The
459[Ford user manual](https://urbit.org/docs/arvo/internals/ford/runes/) has
460details on these and other Ford runes, but understanding these runes isn’t
461necessary for the purposes of this tutorial.
463`fuel:html` gets extra data from your ship’s Eyre module, which handles all
464things HTTP, and sticks it in the subject when Eyre renders a page. This
465includes the name of the ship, which is what we look for in the next line, but
466it includes much more.
468`p.bem.gas` is an expression that looks for `p` within `bem` within `gas`, the
469face that we stored the above `fuel:html` expression in. That wing happens to
470resolve to the name of the ship that is hosting the web-server. If we wanted to
471access the current desk, we would use the wing expression `q.bem.gas`.
473You can take a look at what you can access by typing `fuel:html` in the Dojo,
474and then explore it by modifying your wing expression in your Sail file
475accordingly.
477```
478=/ show-list &
479```
481The above line of code stores a variable used later for a conditional. `&` means
482`true`, meaning that any evaluation of `show-list` will be true until changed in
483the source code.
485```
486=+ wid=(met 3 p.bem.gas)
487=/ what-kind
488 ?: (lte wid 1) "galaxy"
489 ?: =(2 wid) "star"
490 ?: (lte wid 4) "planet"
491 ?: (lte wid 8) "moon"
492 "comet"
493```
495This code chunk is a series of conditionals that checks the host ship’s name to
496see what its value is.
498The first line, `=+ wid=(met 3 p.bem.gas)`, combines a new noun with the
499subject and gives it the face `wid`. This noun is produced by the the
500standard-library function [`met`](../reference/library/2c), which measures the number of
501[blocks](../reference/library/1c/) of size `a` within `b`. Blocks are units that have a
502bitwidth of `2^a`. So, in this case, it measures how many bytes (blocks of size
5033 are bitwidths of 8; `2^3 = 8`) are fully contained in your ship's address.
505Galaxies contain zero to one 3-blocks; stars contain two 3-blocks; planets
506contain three to four 3-blocks; moons contain five to eight 3-blocks; comets
507contain nine to sixteen 3-blocks. Each test uses `lte`, the
508less-than-or-equal-to function, to determine if `wid` is of a given size. The
509result of this series of tests is added to the subject and given the face
510`what-kind`.
512Ship names are simply another representation of atoms, and ship
513categories are different ranges of possible atomic values. Because of this, we
514perform less-than tests to see what kind of ship is running the web-server.
516```
517;html
518 ;head
519 ;meta(charset "utf-8");
520 ;title: A more advanced page
521 ==
522```
524Sail proper begins here. These four lines are four nodes. The `;html` node
525indicates that the contained code should be rendered as HTML; this tag is closed
526on the final line. The `;head` node contains metadata, and is closed three lines
527later. The `;meta` node sets the character set to UTF-8, and the `;title` node
528sets the title of the page that shows up in the browser tab.
530```
531 ;body
532 ;h1: This is the Title
533 ; This is a
534 ;a/"http://urbit.org": link to Urbit.org.
535 ;span: Check it out!
536```
538The first line here opens, of course, the body node. The second line creates
539header text.
541The third, fourth, and fifth lines of code here construct a single line with an
542inline link.
543`; This is a` begins the rendered line with plain text.
545`;a/"http://urbit.org": link to Urbit.org.` continues that same rendered line,
546and creates hyperlinked text out of `link to Urbit.org.`
548`;span: Check it out!` continues that same rendered line further. `;span:`
549has the same use as `<span>` in HTML, and by following it with just a `:` and no
550attributes, it continues the line without formatting. Following `;span:` with
551**two** spaces causes there to be single space following the produced `<span>`
552element, because one of those spaces is syntactically necessary.
554In the following lines, we beginning to use some Hoon expressions again.
556```
557 ;+ ?: show-list
558 ;ol
559```
561In the top line in the chunk above, we use the `;+` Sail rune, which
562resolves an expression to a single node. That expression, in this case, is
563`?:`, which tests the truth value of `show-list`, which we assigned the value
564`&` (meaning “true”) at the beginning of the program. It’s closed by the `==` on
565the last line.
567Thus, this program resolves to the first child of `show-list`, the node `;ol`.
568That node creates an ordered list, and the items contained within that node.
570```
571 ;li(style "color: green"): We're doing more interesting stuff now.
572```
574The line above is the only element of this list that does not use
575Hoon proper. The line renders as green, as interpreted by the browser,
576because of the Sail attribute.
578The next items in the list use `{}`. Recall that these braces, pronounced “lob”
579and “rob” in Hoon-speak, allow for the interpolation of code within a string.
581```
582 ;li: We're pretty-printing this sum with Hoon: {<(add 50 50)>}.
583```
585The line above is interpolated with Hoon code that produces the sum of 50 plus
58650, which is wrapped in `<>` to put the product in double quotes to make it a
587tape, like the rest of the line.
590```
591 ;li: The code above is shorthand for {(scow %ud (add 50 50))}.
592```
594The `{(scow %ud (add 50 50)}` on this line is just a different way
595writing the `{<(add 50 50)>}` expression. `scow` is a Hoon function that turns a
596noun into a `tape`, Hoon’s string type. `%ud` tells `scow` that its argument
597should be displayed in the form of an unsigned decimal.
600```
601 ;li: I am {(trip '~lodleb-ritrul')}.
602```
604`trip` is a function that takes a `cord` and produces a `tape`. A piece of
605data of the cord type is one big atom formed from adjacent unicode bytes and
606delineated by `''`. Remember that all text rendered in Sail is treated as a
607tape. So, by converting `~lodleb-ritrul` to a tape, it can be included in the
608tape that we are trying to insert it into without getting a type error.
610```
611 ;li: Actually, my name is {<p.bem.gas>}. I'm a {what-kind}.
612 ==
613```
615The line above has interpolated code that uses a wing expression to access the
616name of the host ship, and then accesses the face `what-kind` contains a string
617describing what type of ship it is. We declared `gas` on the very first line so
618that we could add that sort of information to the subject that our Sail is
619rendered against. The wing expression `p.bem.gas` looks for `p` within `bem`
620within `gas`, which happens to be where the name of the ship is located.
622Note that `{what-kind}` does not have a `<>` wrapper. Why do you think this is?
624```
625 ;div; My name is {<p.bem.gas>}. I'm a {ship}.
626 ==
627==
628```
630The `;div;` node above is only rendered, as an alternative to the above list,
631if `show-list` is evaluated as true. This means it won’t be shown with your
632default code. To show this line, change the `&` on line 2 to `|` (“false”).
634### Sail Runes
636In the previous example, we used a few special runes that aren’t used in typical
637Hoon code. But there are others that are worth learning about at this point.
639`;+` is an expression that resolves to a single node. We saw this on line 20
640in our Sail example above: `;+ ?: show-list` resolves to the node `;ol` and
641all of that node’s children.
643`;*` is an expression that resolves to a list of nodes. If we want to render
644multiple nodes side-by-side, and not just the children of a single node, this
645is the expression that we use.
647`;=` turns a tuple of nodes into a list of nodes.
650To see how these runes work, let’s take a chunk of our previous code starting at
651line 20 and modify it a bit:
653```
654 ;* ?: show-list
655 ;=
656 ;li: I am {(trip '~lodleb-ritrul')}.
657 ;li: Actually, my name is {<p.bem.gas>}.
658 ==
659 ;=
660 ;div: My name is {<p.bem.gas>}. I'm a {what-kind}.
661 ;div: No list here!
662 ==
663 ==
664```
666We changed the conditional branches so that each contains two Sail nodes,
667side-by-side -- notice that the `;ol` is absent from the first branch.
669We begin with the `;*` rune, which resolves to a list of nodes, instead of the
670`;+` rune, which only resolves to a single node.
672But that’s not enough on its own. `;*` needs to resolve to a list, and a few
673Sail nodes aren’t lists until they are explicitly made into lists. Until we wrap
674those nodes in `;=`, the nodes treated as mere tuples. All branches must be
675valid lists for the `;* ?: show-list` expression to resolve.
678### Conclusion
680You should now have foundational knowledge for making web pages with Sail. There
681is, of course, more to learn. If you’re curious, there’s more advanced
682resources out there.
683The [Ford manual](https://urbit.org/docs/arvo/internals/ford/runes/)
684can show you how to access various ship resources for user in your pages. To
685learn more about how the renderer works, take a look at the
686`/home/ren/urb.hoon` file inside your urbit.