on master help sign in
Merge pull request #1342 from urbit/add-sail-guide
9c2a911tinnus-napbus
content/docs/glossary/sailudon.md+3−2
@@ -8,6 +8,7 @@ category = "arvo"
8
8
9**Sail** and **Udon** are domain specific languages for Hoon.
9**Sail** and **Udon** are domain specific languages for Hoon.
10
10
11−Sail is used to express XML data structures, which are used for rendering webpages, such as when browsing [collections](/docs/glossary/collection) in [Landscape](/docs/glossary/landscape).
11+Sail is used to express XML data structures, which are commonly used for rendering HTML
12+webpages. See the [Sail Guide](/docs/hoon/guides/sail) for more details.
12
13
13−Udon is similar to Markdown, a simple language for writing documents. Udon is stored in [Clay](/docs/glossary/clay) and rendered using [Ford](/docs/glossary/ford).
14+Udon is similar to Markdown, a simple language for writing documents.
content/docs/hoon/guides/_index.md+2
@@ -15,3 +15,5 @@ insert_anchor_links = "right"
15## [Strings](/docs/hoon/guides/strings)
15## [Strings](/docs/hoon/guides/strings)
16
16
17## [JSON](/docs/hoon/guides/json-guide)
17## [JSON](/docs/hoon/guides/json-guide)
18+
19+## [Sail](/docs/hoon/guides/sail)
content/docs/hoon/guides/sail.mdnew file+477
@@ -0,0 +1,477 @@
1++++
2+title = "Sail (HTML)"
3+weight = 6
4+template = "doc.html"
5++++
6+
7+Sail is a domain-specific language for composing HTML (and XML) structures in
8+Hoon. Like everything else in Hoon, a Sail document is a noun, just one produced
9+by a specialized markup language within Hoon.
10+
11+Front-ends for Urbit apps are often created and uploaded separately to the
12+rest of the code in the desk. Sail provides an alternative approach, where
13+front-ends can be composed directly inside agents.
14+
15+This document will walk through the basics of Sail and its syntax.
16+
17+## Basic example
18+
19+It’s easy to see how Sail can directly translate to HTML:
20+
21+<table><thead><tr><th>Sail</th><th>HTML</th></tr></thead>
22+<tbody><tr><td><pre class="language-hoon">
23+;html
24+ ;head
25+ ;title: My page
26+ ;meta(charset "utf-8");
27+ ==
28+ ;body
29+ ;h1: Welcome!
30+ ;p
31+ ; Hello, world!
32+ ; Welcome to my page.
33+ ; Here is an image:
34+ ;br;
35+ ;img@"/foo.png";
36+ ==
37+ ==
38+==
39+</pre></td><td><pre>
40+&#x26;&#x6C;&#x74;&#x3B;html>
41+ &#x26;&#x6C;&#x74;&#x3B;head>
42+ &#x26;&#x6C;&#x74;&#x3B;title>My page&#x26;&#x6C;&#x74;&#x3B;/title>
43+ &#x26;&#x6C;&#x74;&#x3B;meta charset="utf-8" />
44+ &#x26;&#x6C;&#x74;&#x3B;/head>
45+ &#x26;&#x6C;&#x74;&#x3B;body>
46+ &#x26;&#x6C;&#x74;&#x3B;h1>Welcome!&#x26;&#x6C;&#x74;&#x3B;/h1>
47+ &#x26;&#x6C;&#x74;&#x3B;p>Hello, world! Welcome to my
48+ page. Here is an image:
49+ &#x26;&#x6C;&#x74;&#x3B;br />
50+ &#x26;&#x6C;&#x74;&#x3B;img src="/foo.png" />
51+ &#x26;&#x6C;&#x74;&#x3B;/p>
52+ &#x26;&#x6C;&#x74;&#x3B;/body>
53+&#x26;&#x6C;&#x74;&#x3B;/html>
54+</pre></td></tr></tbody></table>
55+
56+## Tags and Closing
57+
58+In Sail, tag heads are written with the tag name prepended by `;`. Unlike in
59+HTML, there are different ways of closing tags, depending on the needs of the
60+tag. One of the nice things about Hoon is that you don’t have to constantly
61+close expressions; Sail inherits this convenience.
62+
63+### Empty
64+
65+Empty tags are closed with a `;` following the tag. For example, `;div;` will be
66+rendered as `&lt;div>&lt;/div>`. Non-container tags `;br;` and `;img@"some-url";` in
67+particular will be rendered as a single tag like `&lt;br />` and `&lt;img src="some-url" />`.
68+
69+### Filled
70+
71+Filled tags are closed via line-break. To fill text inside, add `:` after the
72+tag name, then insert your plain text following a space. Example:
73+
74+| Sail | HTML |
75+| ---------------- | -------------------------- |
76+| `;h1: The title` | `&lt;h1>The title&lt;/h1>` |
77+
78+### Nested
79+
80+To nest tags, simply create a new line. Nested tags need to be closed with `==`,
81+because they expect a list of sub-tags.
82+
83+If we nest lines of plain text with no tag, the text will be wrapped in a
84+`&lt;p>` tag. Additionally, any text with atom auras or `++arm:syntax` in such
85+plain text lines will be wrapped in `&lt;code>` tags.
86+
87+Example:
88+
89+<table><thead><tr><th>Sail</th><th>HTML</th></tr></thead><tbody><tr><td>
90+<pre class="language-hoon">
91+;body
92+ ;h1: Blog title
93+ This is some good content.
94+==
95+</pre></td><td><pre>
96+&#x26;&#x6C;&#x74;&#x3B;body>
97+ &#x26;&#x6C;&#x74;&#x3B;h1>Blog title&#x26;&#x6C;&#x74;&#x3B;/h1>
98+ &#x26;&#x6C;&#x74;&#x3B;p>This is some good content.&#x26;&#x6C;&#x74;&#x3B;/p>
99+&#x26;&#x6C;&#x74;&#x3B;/body>
100+</pre></td></tr></tbody></table>
101+
102+If we want to write a string with no tag at all, then we can prepend
103+those untagged lines with `;` and then a space:
104+
105+<table><thead><tr><th>Sail</th><th>HTML</th></tr></thead><tbody><tr><td>
106+<pre class="language-hoon">
107+;body
108+ ;h1: Welcome!
109+ ; Hello, world!
110+ ; We’re on the web.
111+==
112+</pre></td><td><pre>
113+&#x26;&#x6C;&#x74;&#x3B;body>
114+ &#x26;&#x6C;&#x74;&#x3B;h1>Welcome!&#x26;&#x6C;&#x74;&#x3B;/h1>
115+ Hello, world!
116+ We’re on the web.
117+&#x26;&#x6C;&#x74;&#x3B;/body>
118+</pre></td></tr></tbody></table>
119+
120+## Attributes
121+
122+Adding attributes is simple: just add the desired attribute between parentheses,
123+right after the tag name without a space. We separate different attributes of
124+the same node by using `,`.
125+
126+Attributes can also be specified in tall form, with each key prefixed by `=`,
127+followed by two spaces, and then a tape with its value. These two styles are
128+shown below.
129+
130+### Generic
131+
132+<table><thead><tr><th>Form</th><th>Example</th></tr></thead><tbody>
133+<tr><td>Wide</td><td><pre class="language-hoon">
134+;div(title "a tooltip", style "color:red")
135+ ;h1: Foo
136+ foo bar baz
137+==
138+</pre></td></tr>
139+<tr><td>Tall</td><td><pre class="language-hoon">
140+;div
141+ =title "a tooltip"
142+ =style "color:red"
143+ ;h1: Foo
144+ foo bar baz
145+==
146+</pre></td></tr>
147+<tr><td>HTML</td><td><pre>
148+&#x26;&#x6C;&#x74;&#x3B;div title="a tooltip" style="color:red">
149+ &#x26;&#x6C;&#x74;&#x3B;h1>Foo&#x26;&#x6C;&#x74;&#x3B;/h1>
150+ &#x26;&#x6C;&#x74;&#x3B;p>foo bar baz &#x26;&#x6C;&#x74;&#x3B;/p>
151+&#x26;&#x6C;&#x74;&#x3B;/div>
152+</pre></td></tr></tbody></table>
153+
154+### IDs
155+
156+Add `#` after tag name to add an ID:
157+
158+| Sail | HTML |
159+| ------------------- | ----------------------------------- |
160+| `;nav#header: Menu` | `&lt;nav id="header">Menu&lt;/nav>` |
161+
162+### Classes
163+
164+Add `.` after tag name to add a class:
165+
166+| Sail | HTML |
167+| ---------------------- | ---------------------------------------- |
168+| `;h1.text-blue: Title` | `&lt;h1 class="text-blue">Title&lt;/h1>` |
169+
170+For class values containing spaces, you can add additional `.`s like so:
171+
172+| Sail | HTML |
173+| ------------------- | --------------------------------------- |
174+| `;div.foo.bar.baz;` | `&lt;div class="foo bar baz">&lt;/div>` |
175+
176+Otherwise, if your class value does not conform to the allowed `@tas`
177+characters, you must use the generic attribute syntax:
178+
179+| Sail | HTML |
180+| ------------------------ | ----------------------------------- |
181+| `;div(class "!!! !!!");` | `&lt;div class="!!! !!!">&lt;/div>` |
182+
183+### Images
184+
185+Add `@` after the tag name to link your source:
186+
187+| Sail | HTML |
188+| --------------------- | ----------------------------- |
189+| `;img@"example.png";` | `&lt;img src="example.png"/>` |
190+
191+To add attributes to the image, like size specifications, add the desired
192+attribute after the `"` of the image name and before the final `;` of the `img`
193+tag like ;img@"example.png"(width "100%");`.
194+
195+### Links
196+
197+Add `/` after tag name to start an `href`.
198+
199+<table><tr><th>Sail</th></tr><tr><td><pre class="language-hoon">
200+;a/"urbit.org": A link to Urbit.org
201+</pre></td></tr><tr><th>HTML</th></tr><tr><td><pre>
202+&#x26;&#x6C;&#x74;&#x3B;a href="urbit.org">A link to Urbit.org&#x26;&#x6C;&#x74;&#x3B;/a>
203+</pre></td></tr></table>
204+
205+## Interpolation
206+
207+The textual content of tags, despite not being enclosed in double-quotes, are
208+actually tapes. This means they support interpolated Hoon expressions in the
209+usual manner. For example:
210+
211+<table><thead><tr><th>Sail</th><th>HTML</th></tr></thead><tbody><tr><td>
212+<pre class="language-hoon">
213+=| =time
214+;p: foo {&#x26;&#x6C;&#x74;&#x3B;time>} bar
215+</pre></td><td><pre>
216+&#x26;&#x6C;&#x74;&#x3B;p>foo ~2000.1.1 baz&#x26;&#x6C;&#x74;&#x3B;/p>
217+</pre></td></tr></tbody></table>
218+
219+Likewise:
220+
221+<table><tr><th>Sail</th></tr><tr><td><pre class="language-hoon">
222+=/ txt=tape " bananas"
223+;article
224+ ;b: {(a-co:co (mul 42 789))}
225+ ; {txt}
226+ {&#x26;&#x6C;&#x74;&#x3B;our>} {&#x26;&#x6C;&#x74;&#x3B;now>} {&#x26;&#x6C;&#x74;&#x3B;`@ux`(end 6 eny)>}
227+==
228+</pre></td></tr><tr><th>HTML</th></tr><tr><td><pre>
229+&#x26;&#x6C;&#x74;&#x3B;article>
230+ &#x26;&#x6C;&#x74;&#x3B;b>33138&#x26;&#x6C;&#x74;&#x3B;/b> bananas
231+ &#x26;&#x6C;&#x74;&#x3B;p>~zod ~2022.2.21..09.54.21..5b63 0x9827.99c7.06f4.8ef9&#x26;&#x6C;&#x74;&#x3B;/p>
232+&#x26;&#x6C;&#x74;&#x3B;/article>
233+</pre></td></tr></table>
234+
235+## A note on CSS
236+
237+The CSS for a page is usually quite large. The typical approach is to include a
238+separate arm in your agent (`++style` or the like) and write out the CSS in a
239+fenced cord block. You can then call `++trip` on it and include it in a style
240+tag. For example:
241+
242+```hoon
243+++ style
244+ ^~
245+ %- trip
246+ '''
247+ main {
248+ width: 100%;
249+ color: red;
250+ }
251+ header {
252+ color: blue;
253+ font-family: monospace;
254+ }
255+ '''
256+```
257+
258+And then your style tag might look like:
259+
260+```hoon
261+;style: {style}
262+```
263+
264+A cord is used rather than a tape so you don't need to escape braces. The
265+[ketsig](/docs/hoon/reference/rune/ket#-ketsig) (`^~`) rune means `++trip` will
266+be run at compile time rather than call time.
267+
268+## Types and marks
269+
270+So far we've shown rendered HTML for demonstrative purposes, but Sail syntax
271+doesn't directly produce HTML text. Instead, it produces a
272+[$manx](/docs/hoon/reference/stdlib/5e#manx). This is a Hoon type used to
273+represent an XML hierarchical structure with a single root node. There are six
274+XML-related types defined in the standard library:
275+
276+```hoon
277++$ mane $@(@tas [@tas @tas]) :: XML name+space
278++$ manx $~([[%$ ~] ~] [g=marx c=marl]) :: dynamic XML node
279++$ marl (list manx) :: XML node list
280++$ mars [t=[n=%$ a=[i=[n=%$ v=tape] t=~]] c=~] :: XML cdata
281++$ mart (list [n=mane v=tape]) :: XML attributes
282++$ marx $~([%$ ~] [n=mane a=mart]) :: dynamic XML tag
283+```
284+
285+More information about these can be found in [section 5e of the standard library
286+reference](/docs/hoon/reference/stdlib/5e).
287+
288+You don't need to understand these types in order to write Sail. The main thing
289+to note is that a `$manx` is a node (a single tag) and its contents is a
290+[$marl](/docs/hoon/reference/stdlib/5e#marl), which is just a `(list manx)`.
291+
292+### Rendering
293+
294+A `$manx` can be rendered as HTML in a tape with the `++en-xml:html` function in
295+`zuse.hoon`. For example:
296+
297+```
298+> ;p: foobar
299+[[%p ~] [[%$ [%$ "foobar"] ~] ~] ~]
300+
301+> =x ;p: foobar
302+
303+> (en-xml:html x)
304+"<p>foobar</p>"
305+
306+> (crip (en-xml:html x))
307+'<p>foobar</p>'
308+```
309+
310+### Sanitization
311+
312+The `++en-xml:html` function will sanitize the contents of both attributes and
313+elements, converting characters such as `>` to HTML entities. For example:
314+
315+```
316+> =z ;p(class "\"><script src=\"example.com/xxx.js"): <h1>FOO</h1>
317+
318+> (crip (en-xml:html z))
319+'<p class="&quot;&gt;&lt;script src=&quot;example.com/xxx.js">&lt;h1&gt;FOO&lt;/h1&gt;</p>'
320+```
321+
322+### Marks
323+
324+There are a few different HTML and XML related marks, so it can be a bit
325+confusing. We'll look at the ones you're most likely to use.
326+
327+#### `%html`
328+
329+- Type: `@t`
330+
331+This mark is used for HTML that has been printed as text in a cord. You may wish
332+to return this mark when serving pages to the web. To do so, you must run the
333+`$manx` produced by your Sail expressions through `++en-xml:html`, and then run
334+the resulting `tape` through `++crip`.
335+
336+#### `%hymn`
337+
338+- Type: `$manx`
339+
340+The `%hymn` mark is intended to be used for complete HTML documents - having an
341+`&lt;html>` root element, `&lt;head>`, `&lt;body>`, etc. This isn't enforced on
342+the type level but it is assumed in certain mark conversion pathways. Eyre can
343+automatically convert a `%hymn` to printed `%html` if it was requested through
344+Eyre's scry interface.
345+
346+#### `%elem`
347+
348+- Type: `$manx`
349+
350+The type of the `%elem` mark is a `$manx`, just like a `%hymn`. While `%hymn`s
351+are intended for complete HTML documents, `%elem`s are intended for more general
352+XML structures. You may wish to use an `%elem` mark if you're producing smaller
353+fragments of XML or HTML rather than whole documents. Like a `%hymn`, Eyre can
354+automatically convert it to `%html` if requested through its scry interface.
355+
356+#### Summary
357+
358+In general, if you're going to be composing web pages and serving them to web
359+clients, running the result of your Sail through `++en-xml:html`, `++crip`ping
360+it and producing `%html` is the most straight-forward approach. If you might
361+want to pass around a `$manx` to other agents or ships which may wish to
362+manipulate it futher, a `%hymn` or `%elem` is better.
363+
364+## Sail Runes
365+
366+In addition to the syntax so far described, there are also a few Sail-specific
367+runes:
368+
369+### `;+` Miclus
370+
371+The [miclus rune](/docs/hoon/reference/rune/mic#-miclus) makes a `$marl` from a
372+complex hoon expression that produces a single `$manx`. Its main use is nesting
373+tall-form hoon logic in another Sail element. For example:
374+
375+```hoon
376+;p
377+ ;b: {(a-co:co number)}
378+ ; is an
379+ ;+ ?: =(0 (mod number 2))
380+ ;b: even
381+ ;b: odd
382+ ; number.
383+==
384+```
385+
386+Produces one of these depending on the value of `number`:
387+
388+```
389+<p><b>2 </b>is an <b>even </b>number.</p>
390+```
391+
392+```
393+<p><b>12345 </b>is an <b>odd </b>number.</p>
394+```
395+
396+### `;*` Mictar
397+
398+The [mictar rune](/docs/hoon/reference/rune/mic#-mictar) makes a `$marl` (a list
399+of XML nodes) from a complex hoon expression. This rune lets you add many
400+elements inside another Sail element. For example:
401+
402+<table><thead><tr><th>Sail</th><th>HTML</th></tr></thead><tbody><tr><td>
403+<pre class="language-hoon">
404+=/ nums=(list @ud) (gulf 1 9)
405+;p
406+ ;* %+ turn nums
407+ |= n=@ud
408+ ?: =(0 (mod n 2))
409+ ;sup: {(a-co:co n)}
410+ ;sub: {(a-co:co n)}
411+==
412+</pre></td><td><pre>
413+&#x26;&#x6C;&#x74;&#x3B;p>
414+ &#x26;&#x6C;&#x74;&#x3B;sub>1&#x26;&#x6C;&#x74;&#x3B;/sub>&#x26;&#x6C;&#x74;&#x3B;sup>2&#x26;&#x6C;&#x74;&#x3B;/sup>
415+ &#x26;&#x6C;&#x74;&#x3B;sub>3&#x26;&#x6C;&#x74;&#x3B;/sub>&#x26;&#x6C;&#x74;&#x3B;sup>4&#x26;&#x6C;&#x74;&#x3B;/sup>
416+ &#x26;&#x6C;&#x74;&#x3B;sub>5&#x26;&#x6C;&#x74;&#x3B;/sub>&#x26;&#x6C;&#x74;&#x3B;sup>6&#x26;&#x6C;&#x74;&#x3B;/sup>
417+ &#x26;&#x6C;&#x74;&#x3B;sub>7&#x26;&#x6C;&#x74;&#x3B;/sub>&#x26;&#x6C;&#x74;&#x3B;sup>8&#x26;&#x6C;&#x74;&#x3B;/sup>
418+ &#x26;&#x6C;&#x74;&#x3B;sub>9&#x26;&#x6C;&#x74;&#x3B;/sub>
419+&#x26;&#x6C;&#x74;&#x3B;/p>
420+</pre></td></tr></tbody></table>
421+
422+### `;=` Mictis
423+
424+The [mictis rune](/docs/hoon/reference/rune/mic#-mictis) makes a `$marl` (a list
425+of XML nodes) from a series of `$manx`es. This is mostly useful if you want to
426+make the list outside of an element and then be able to insert it afterwards.
427+For example:
428+
429+<table><thead><tr><th>Sail</th><th>HTML</th></tr></thead><tbody><tr><td>
430+<pre class="language-hoon">
431+=/ paras=marl
432+ ;= ;p: First node.
433+ ;p: Second node.
434+ ;p: Third node.
435+ ==
436+;main
437+ ;* paras
438+==
439+</pre></td><td><pre>
440+&#x26;&#x6C;&#x74;&#x3B;main>
441+ &#x26;&#x6C;&#x74;&#x3B;p>First node.&#x26;&#x6C;&#x74;&#x3B;/p>
442+ &#x26;&#x6C;&#x74;&#x3B;p>Second node.&#x26;&#x6C;&#x74;&#x3B;/p>
443+ &#x26;&#x6C;&#x74;&#x3B;p>Third node.&#x26;&#x6C;&#x74;&#x3B;/p>
444+&#x26;&#x6C;&#x74;&#x3B;/main>
445+</pre></td></tr></tbody></table>
446+
447+### `;/` Micfas
448+
449+The [micfas rune](/docs/hoon/reference/rune/mic#-micfas) turns an ordinary tape
450+into a `$manx`. For example:
451+
452+```
453+> %- en-xml:html ;/ "foobar"
454+"foobar"
455+```
456+
457+In order to nest it inside another Sail element, it must be preceeded with a
458+`;+` rune or similar, it cannot be used directly. For example:
459+
460+```hoon
461+;p
462+ ;+ ;/ ?: =(0 (mod eny 2))
463+ "even"
464+ "odd"
465+==
466+```
467+
468+## Good examples
469+
470+Here's a couple of agents that make use of Sail, which you can use as a
471+reference:
472+
473+- [Pals by ~palfun-foslup][pals]
474+- [Gora by ~rabsef-bicrym][gora]
475+
476+[pals]: https://github.com/Fang-/suite/blob/master/app/pals/webui/index.hoon
477+[gora]: https://github.com/dalten-collective/gora/blob/master/sail/app/gora/goraui/index.hoon