the ugly ocaml monstrosity powering my site oppi.li
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

ocaml time

Signed-off-by: oppiliappan <me@oppi.li>

+5575 -13064
+4
.gitignore
··· 1 + _build 2 + dune.lock 3 + .direnv 4 + .envrc
+74
.ocamlformat
··· 1 + comment-check=true 2 + debug=false 3 + disable=false 4 + margin-check=false 5 + max-iters=10 6 + ocaml-version=5.4.0 7 + quiet=false 8 + disable-conf-attrs=false 9 + version-check=true 10 + assignment-operator=end-line 11 + break-before-in=fit-or-vertical 12 + break-cases=fit 13 + break-collection-expressions=fit-or-vertical 14 + break-colon=after 15 + break-fun-decl=wrap 16 + break-fun-sig=wrap 17 + break-infix=wrap 18 + break-infix-before-func=false 19 + break-separators=after 20 + break-sequences=true 21 + break-string-literals=auto 22 + break-struct=force 23 + cases-exp-indent=4 24 + cases-matching-exp-indent=normal 25 + disambiguate-non-breaking-match=false 26 + doc-comments=after-when-possible 27 + doc-comments-padding=2 28 + doc-comments-tag-only=default 29 + dock-collection-brackets=true 30 + exp-grouping=preserve 31 + extension-indent=2 32 + field-space=loose 33 + function-indent=2 34 + function-indent-nested=never 35 + if-then-else=compact 36 + indent-after-in=0 37 + indicate-multiline-delimiters=no 38 + indicate-nested-or-patterns=unsafe-no 39 + infix-precedence=indent 40 + leading-nested-match-parens=false 41 + let-and=compact 42 + let-binding-indent=2 43 + let-binding-deindent-fun=true 44 + let-binding-spacing=compact 45 + let-module=compact 46 + letop-punning=preserve 47 + line-endings=lf 48 + margin=80 49 + match-indent=0 50 + match-indent-nested=never 51 + max-indent=68 52 + module-indent=2 53 + module-item-spacing=compact 54 + nested-match=wrap 55 + ocp-indent-compat=false 56 + parens-ite=false 57 + parens-tuple=always 58 + parens-tuple-patterns=multi-line-only 59 + parse-docstrings=true 60 + parse-toplevel-phrases=false 61 + sequence-blank-line=preserve-one 62 + sequence-style=terminator 63 + single-case=compact 64 + space-around-arrays=true 65 + space-around-lists=true 66 + space-around-records=true 67 + space-around-variants=true 68 + stritem-extension-indent=0 69 + type-decl=compact 70 + type-decl-indent=2 71 + wrap-comments=false 72 + wrap-fun-args=true 73 + profile=default 74 +
+40
_site/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>oppi.li</title><link rel="stylesheet" href="/style.css?v=8a1069aa" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"></nav><main class="px-2"><div class="max-w-xl mx-auto"> 3 + <style> 4 + .sphere-clip{ 5 + width:100%; 6 + aspect-ratio:1; 7 + border-radius:50%; 8 + overflow:hidden; 9 + max-height:100%; 10 + } 11 + .sphere{ 12 + --grad-start:0%; 13 + --grad-stop:80%; 14 + --rotation:0%; 15 + --fg:#fff; 16 + --bg:#000; 17 + position:relative; 18 + width:100%; 19 + height:100%; 20 + background:var(--bg); 21 + overflow:hidden; 22 + filter:url(#dither); 23 + } 24 + .sphere::before{ 25 + content:""; 26 + position:absolute; 27 + top:0; 28 + left:0; 29 + width:400%; 30 + height:100%; 31 + background:repeat-x var(--rotation) 0/50% 100% radial-gradient(circle at 50% 0,transparent var(--grad-start),var(--fg) var(--grad-stop)); 32 + mask:radial-gradient(circle at 50% 0,transparent 5%,#000 calc(var(--grad-stop) + 15%)) 0 0/50% 100%; 33 + } 34 + @media(prefers-color-scheme:dark){ 35 + .sphere{ 36 + --fg:#000; 37 + --bg:#fff; 38 + } 39 + } 40 + </style><div class="flex flex-row p-2 mt-12 gap-12"><svg style="position:absolute;width:0;height:0;overflow:hidden" aria-hidden="true"><defs><filter id="dither" color-interpolation-filters="sRGB" x="0" y="0" width="100%" height="100%"><feImage width="8" height="8" result="pat" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA+UlEQVR42gXBERTCUABA0X/OYDAYDAZBEAyCIBgMgiAIgiAYBINgEAwGgyAIBsFgMAiCIAiCIAgGQTAYDAaDIAiCwWDwulcIIXg8HgwGA36/H4qi8Hq9sCyLtm0Rm82G0WjE5XJhvV4ThiHT6ZT7/U4QBIhut0tVVaiqSpZl9Pt9vt8vnU6HsiwRh8OB5XLJfr9nNptxPp9xXZckSbBtGyHLMs/nE9M0aZoGSZJI05ThcEhd14jdbsdkMuF2u+H7PtvtlvF4zPV6xfM8hGEYfD4fdF2nKAp6vR7v9xtN08jzHHE6nVitVsRxzGKx4Hg84jgOURQxn8/5A7oKnYRU4EpfAAAAAElFTkSuQmCC"/><feTile in="pat" result="tiled"/><feComposite operator="arithmetic" k1="0" k2="1" k3="1" k4="-0.5" in="SourceGraphic" in2="tiled"/><feComponentTransfer><feFuncR type="discrete" tableValues="0 1"/><feFuncG type="discrete" tableValues="0 1"/><feFuncB type="discrete" tableValues="0 1"/></feComponentTransfer></filter></defs></svg><div class="aspect-1/1 w-48 flex items-center justify-center"><div class="sphere-clip"><div class="sphere"></div></div></div><div class="text-justify"><p>I&#x27;m Akshay, programmer, pixel-artist &amp; programming-language enthusiast.</p><p>I am currently building <a href="https://tangled.org">Tangled</a>, a new social-enabled code-collaboration platform.</p><p>Reach out at oppili@libera.chat.</p></div></div><div><div class="space-y-4"><div class="flex items-center justify-between"><h2>posts</h2><a class="mt-10" href="/posts">view all</a></div><div><div class="flex"><a href="posts/mounting_the_atmosphere">mounting the atmosphere</a><span class="grow text-right tabular-nums">31.07.2025</span></div><div class="flex"><a href="posts/configuring_jujutsu">configuring jujutsu</a><span class="grow text-right tabular-nums">24.05.2025</span></div><div class="flex"><a href="posts/tales_from_mainframe_modernization">tales from mainframe modernization</a><span class="grow text-right tabular-nums">21.05.2025</span></div></div></div><div><div class="flex items-center justify-between"><h2>weeklies</h2><a class="mt-10" href="/weeklies">view all</a></div><div><div class="flex flex-row items-start"><div class="flex flex-col mt-4 items-center"><a class="text-lg" href="weeklies/2026-16">16</a><span class="text-xs">2026</span></div><ul><li>FP Launchpad at IIT Madras</li><li>Renewed interest in OCaml</li><li>Building a web-of-trust</li></ul></div></div></div></div></div></main></div></body></html>
+50
_site/posts/OSC-52/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>OSC-52</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">OSC-52</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">OSC-52</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>33 sentences</span><span>·</span><span>33.53 cm</span><span>·</span><span>27.11.2024</span></div><p>I use <code>ssh</code> a lot. Copying text from the remote machine to 3 + the host machine always sucked. But OSC-52 makes that easy.</p> 4 + <p>OSC-52 is an ANSI escape sequence to write text to the 5 + terminal emulator. The terminal emulator, if it understands 6 + what is going on, will in turn write this text to the system 7 + clipboard.</p> 8 + <p>What this means is some <code>printf</code> magic can send text to your 9 + clipboard. I store this one-liner in a script called 10 + <code>oclip</code>:</p> 11 + <pre><code class="language-bash">printf &quot;\033]52;c;%s\007&quot; &quot;$(base64 &lt;&amp;0)&quot; 12 + </code></pre> 13 + <p>and I run it with:</p> 14 + <pre><code class="language-bash">remote $ cat some_file.txt | oclip 15 + 16 + # some_file.txt's contents are now the host's clipboard 17 + </code></pre> 18 + <h3 id="the-catch">The catch</h3> 19 + <p>Your terminal emulator must support OSC-52, <code>alacritty</code> and 20 + <code>termux</code> seem to support this out of the box. In <code>st</code>, 21 + OSC-52 works with this change to <code>config.h</code>:</p> 22 + <pre><code>int allowwindowops = 1; 23 + </code></pre> 24 + <p>If you are using <code>tmux</code>, you need to flip this switch on:</p> 25 + <pre><code>set -s set-clipboard on 26 + </code></pre> 27 + <p>If you are inside <code>nvim</code>, it may work as expected as long as 28 + <code>$SSH_TTY</code> is set. I sometimes physically start a session, 29 + and <code>ssh</code> into the same session later from another machine, 30 + and <code>$SSH_TTY</code> remains unset, so I force OSC-52 in <code>nvim</code> at 31 + all times (see 32 + <a href="https://neovim.io/doc/user/provider.html#clipboard-osc52">nvimdoc</a>):</p> 33 + <pre><code class="language-lua">vim.g.clipboard = { 34 + name = 'OSC 52', 35 + copy = { 36 + ['+'] = require('vim.ui.clipboard.osc52').copy('+'), 37 + ['*'] = require('vim.ui.clipboard.osc52').copy('*'), 38 + }, 39 + paste = { 40 + ['+'] = require('vim.ui.clipboard.osc52').paste('+'), 41 + ['*'] = require('vim.ui.clipboard.osc52').paste('*'), 42 + }, 43 + } 44 + </code></pre> 45 + <p>If you are inside <code>nvim</code> inside <code>tmux</code> inside an <code>ssh</code> 46 + session inside <code>st</code>, you neeed all of the above tweaks. 47 + <code>nvim</code> will pass the contents around to <code>tmux</code>, which in 48 + turn will pass the contents to <code>st</code>, which should pass it to 49 + your system clipboard.</p> 50 + </div></main></div></body></html>
+233
_site/posts/SDL2_devlog/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>SDL2 devlog</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">SDL2 devlog</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">SDL2 devlog</h1><div class="text-center space-x-2"><span>7 min</span><span>·</span><span>145 sentences</span><span>·</span><span>158.00 cm</span><span>·</span><span>11.04.2021</span></div><p>I have been working on an editor for the <a href="https://git.peppe.rs/graphics/obi/about">One Bit 3 + Image</a> file format 4 + in Rust and SDL2. This entry in my blog follows my progress 5 + on the editor. The days are listed in reverse chronological 6 + order, begin from the bottom, if this is your first time on 7 + this page.</p> 8 + <h3 id="day-20">Day 20</h3> 9 + <p>More <code>lisp</code> stuff! I added a new brush, for rectangular 10 + selections. While selection doesn't do much on its own, the 11 + selected area can be passed onto a <code>lisp</code> procedure, for 12 + example, a procedure to draw horizontal black and white 13 + lines:</p> 14 + <p><img src="https://cdn.oppi.li/frU.mp4" alt="Day 20" /></p> 15 + <h3 id="day-19">Day 19</h3> 16 + <p>Attempted <a href="https://peppe.rs/art/conduit.png">some isometric 17 + art</a> within the editor. 18 + The angles displayed alongside the line brush are handly, 19 + however, having only a rectangular grid did not help. I 20 + implemented an isometric grid today. Isometric grids in 21 + pixel art differ in that the tangent of the isometric angle 22 + is exactly 0.5! For every pixel down, you go exactly two 23 + pixels sideways. The math works out really well in the 24 + drawing procedures too, dealing with floating points is a 25 + pain.</p> 26 + <p><img src="https://cdn.oppi.li/1Kb.png" alt="Day 19" /></p> 27 + <h3 id="day-18">Day 18</h3> 28 + <p>I added basic support for guides, they can be added and 29 + activated from the <code>lisp</code> REPL. Another long standing 30 + improvement I wanted to make was reworking the pixmap 31 + drawing procedure. The old procedure draws a square for each 32 + pixel in the pixmap, coloured according to its value in the 33 + pixmap. Naturally, this means, for an <strong>NxN</strong> pixmap, there 34 + are <strong>N²</strong> calls to SDL! I reworked this procedure to 35 + compress each line of the pixmap using RLE (run length 36 + encoding), and call out to SDL for each run in the line. 37 + This drastically improved drawing speeds on larger grids. 38 + The following is a comparison between the two procedures, 39 + the leftmost picture is the rendered image, the middle 40 + picture is the optimized drawing procedure (draws each run 41 + instead of pixel), and the right most picture is the 42 + primitive drawing procedure (draws each pixel):</p> 43 + <p><img src="https://cdn.oppi.li/U4B.png" alt="Day 18" /></p> 44 + <h3 id="day-17">Day 17</h3> 45 + <p>I decided to give the text-only statusline a touch up, by 46 + adding a active color and dither level preview. Aligning the 47 + &quot;widget&quot; to the right of statusline involved a lot more than 48 + I thought, so I created a ghetto CSS-like rectangle 49 + placement system to position containers inside containers:</p> 50 + <pre><code class="language-rust">// roughly something like this 51 + let statusline = 52 + Container::new(Offset::Left(0), Offset::Bottom(40)) 53 + .width(Size::Max) 54 + .height(Size::Absolute(20)); 55 + 56 + let mut primary = Container::uninit() 57 + .width(Size::Absolute(16)) 58 + .height(Size::Absolute(16)); 59 + 60 + container.place( 61 + &amp;mut padding_box, 62 + HorAlign::Right, 63 + VertAlign::Center 64 + ); 65 + </code></pre> 66 + <p>The result (brush preview on the bottom right):</p> 67 + <p><img src="https://cdn.oppi.li/OtU.mp4" alt="Day 17" /></p> 68 + <h3 id="day-16">Day 16</h3> 69 + <p>The embedded lisp is coming along nicely, users can load 70 + a custom <code>rc.lisp</code>, which is evaluated on startup. To 71 + disable to grid on start, for example:</p> 72 + <pre><code class="language-scheme">;;; rc.lisp 73 + (toggle-grid) 74 + </code></pre> 75 + <p>Some aliases to switch between brushes:</p> 76 + <pre><code class="language-scheme">;;; rc.lisp 77 + (define (brush kind) 78 + (cond 79 + ((eq? kind 'f) (brush-fill)) 80 + ((eq? kind 'c) (brush-circle)) 81 + ((eq? kind 'l) (brush-line)) 82 + ((eq? kind 'l+) (brush-line-extend)) 83 + (else (brush-circle)))) 84 + </code></pre> 85 + <p>The following script draws a straight line along a given 86 + axis, at a given distance from the canvas boundary:</p> 87 + <p><img src="https://cdn.oppi.li/b3i.mp4" alt="Day 16" /></p> 88 + <h3 id="day-15">Day 15</h3> 89 + <p>I began writing a standard library for the lisp, in lisp. It 90 + includes basic list operations: <code>car</code>, <code>cdr</code>, <code>null?</code>, 91 + <code>list</code>, higher order functions: <code>map</code>, <code>filter</code>, <code>fold</code>:</p> 92 + <pre><code class="language-lisp">(define (member? item ls) 93 + (fold #f 94 + (lambda (acc x) (or acc (eq? item x))) 95 + ls)) 96 + </code></pre> 97 + <h3 id="day-14">Day 14</h3> 98 + <p>I attempted a <a href="https://peppe.rs/art/ramen_noodles.png">small art 99 + piece</a> using the 100 + editor, while it was largely usable, I felt a certain lack 101 + of feedback. The brushes just didn't relay as much info as 102 + I'd have liked, for example, the approximate points of the 103 + line or the angle made by the line against the x-axis. 104 + Unfortunately, the existing infrastructure around brushes 105 + and line drawing didn't easily allow for this either. I went 106 + ahead and reimplemented brushes, and added a new flood fill 107 + brush too:</p> 108 + <p><img src="https://cdn.oppi.li/8q.mp4" alt="Day 14" /></p> 109 + <h3 id="day-13">Day 13</h3> 110 + <p>I added a few more forms to the <code>lisp</code> evaluator. It handles 111 + recursion, definitions, variable mutation and more. The 112 + prelude contains 20 subroutines so far, including 113 + comparision and logic operators. The REPL interface on the 114 + SDL side requires some UX tweaks; environment based 115 + completion, readline motions sound doable.</p> 116 + <p><img src="https://cdn.oppi.li/u3.mp4" alt="Day 13" /></p> 117 + <h3 id="day-12">Day 12</h3> 118 + <p>I lifted most of 119 + <a href="https://github.com/murarth/ketos">murarth/ketos</a> into the 120 + editor. <code>ketos</code>'s implementation of <code>lisp</code> is too vast for 121 + my use case. For example, the editor does not need data 122 + types to handle raw strings or byte strings. I have got a 123 + basic evaluator running inside the SDL2 context (notice the 124 + <code>lisp</code> REPL at the bottom of the window). Over the 125 + following days, I intend to create a set of prelude 126 + functions to manipulate the pixmap. Users can implement 127 + their own brushes, dithering patterns, keybinds and more 128 + (hopefully).</p> 129 + <p><img src="https://cdn.oppi.li/y0.mp4" alt="Day 12" /></p> 130 + <h3 id="day-11">Day 11</h3> 131 + <p>I intend to supplement the editor with scripting language 132 + and an inbuilt REPL for the same. I began by implementing a 133 + text box widget from scratch, with history and readline like 134 + editing:</p> 135 + <p><img src="https://cdn.oppi.li/Mh.mp4" alt="Day 11" /></p> 136 + <h3 id="day-10">Day 10</h3> 137 + <p>I started reading up on dithering methods and half-toning, I 138 + wanted to create a dithering brush that would automatically 139 + produce popular dithering patterns. The method that caught 140 + my eye (and also the one used most often in pixel art), was 141 + Bayer's ordered dithering. When applied to a black and white 142 + image, each pixel, based on its intensity, is mapped to a 143 + 4x4 grid of pixels. A completely empty (completely black) 144 + 4x4 grid represents zero intensity, and a filled 4x4 grid 145 + represents full intensity. Bayer's ordered dithering can 146 + produce 15 steps of intensity between zero and full (by 147 + switching on exactly 1 pixel more at each level), thus, 148 + being able to draw 17 &quot;shades&quot; from white to black. Creating 149 + a dithering brush from here was fairly trivial. Our pixmap 150 + is supposed to represent the final dithered image, it must 151 + be divided into 4x4 grids. Each grid is colored based on the 152 + intensity of the brush passing over it:</p> 153 + <p><img src="https://cdn.oppi.li/Mn.png" alt="Day 10" /></p> 154 + <h3 id="day-9">Day 9</h3> 155 + <p>I started working towards an interface. I like the idea of a 156 + largely read-only HUD, i. e., an interface that simply 157 + describes the state of the application. Changes to this 158 + state are initiated via keybinds or text commands. I am 159 + proud of the symmetry indicator; <code>-</code> for horizontal 160 + symmetry, <code>|</code> for vertical symmetry, <code>+</code> for radial 161 + symmetry.</p> 162 + <p><img src="https://cdn.oppi.li/hx.png" alt="Day 9" /></p> 163 + <h3 id="day-8">Day 8</h3> 164 + <p>One of my favourite features of GIMP was symmetric editing. 165 + I added some coordinate geometry primitives to my pixmap 166 + abstraction, allowing for mirroring and reflecting figures 167 + about lines or points. The result was an ergonomic function 168 + that applies symmetry to any painting operation, (undo/redo 169 + works as expected):</p> 170 + <pre><code class="language-rust">let line = self.pixmap.get_line(start, end); 171 + let sym_line = self.symmetry.apply(&amp;line); 172 + for point on line.extend(sym_line) { 173 + // draw to window 174 + } 175 + </code></pre> 176 + <p><img src="https://cdn.oppi.li/B1.mp4" alt="Day 8" /></p> 177 + <h3 id="day-7">Day 7</h3> 178 + <p>Bresenham saves the day again! This time, I implemented his 179 + line drawing algorithm, to, well, draw lines. Each point on 180 + the line is then &quot;buffed&quot; based on the active brush size. 181 + Today's changes fit in very well with the undo system and 182 + the brush size feature. Creating the right abstractions, one 183 + at a time :)</p> 184 + <p><img src="https://cdn.oppi.li/xt.mp4" alt="Day 7" /></p> 185 + <h3 id="day-6">Day 6</h3> 186 + <p>I extended Bresenham's algorithm to draw not just circle 187 + outlines, but also generate their fills. Unlike Bresenham's 188 + algorithm, this variant generates points for two quadrants 189 + at once, these points are mirrored over the dividing axis to 190 + generate the other two quadrants.</p> 191 + <p><img src="https://cdn.oppi.li/f3.png" alt="Day 6" /></p> 192 + <h3 id="day-5">Day 5</h3> 193 + <p>I discovered and implemented Bresenham's algorithm for 194 + efficient circle drawing. The algorithm allowed for sized 195 + circular brushes, something I really liked from GIMP. Very 196 + convenient that the Wikipedia page for Bresenham's algorithm 197 + also includes a section about optimizing for integer based 198 + arithmetic. I managed to abstract out another giant 199 + component of the application, the pixmap. Any image is just 200 + a grid of pixels (a pixmap), where the pixel's value is 201 + decided by the application (1-bit in my case). I could 202 + potentially extend the application to a 24-bit image editor!</p> 203 + <p><img src="https://cdn.oppi.li/Kh.mp4" alt="Day 5" /></p> 204 + <h3 id="day-4">Day 4</h3> 205 + <p>I created a generic &quot;undo stack&quot; data structure that allows 206 + for infinite &quot;undos&quot; and &quot;redos&quot;. Every modification 207 + operation to the grid is persisted to the application state. 208 + A couple of keybinds allow the user to revert and re-apply 209 + these operations! I expect abstracting this component will 210 + come in handy down the line.</p> 211 + <p><img src="https://cdn.oppi.li/w5.mp4" alt="Day 4" /></p> 212 + <h3 id="day-3">Day 3</h3> 213 + <p>I implemented the bare minimum required to call the program 214 + an &quot;editor&quot;. The application displays a grid, tracks mouse 215 + events, paints white to the canvas on left click, and black 216 + to the canvas on right click. I created a make-shift MVC 217 + architecture à la Elm in Rust.</p> 218 + <p><img src="https://cdn.oppi.li/GF.mp4" alt="Day 3" /></p> 219 + <h3 id="day-2">Day 2</h3> 220 + <p>I started figuring out event handling today. Implemented a 221 + couple of keybinds to zoom in/out of the drawing area. 222 + Conversions of SDL2 coordinates (measured in signed 32 bit 223 + integers) to my internal &quot;drawing area&quot; coordinates 224 + (measured in unsigned 32 bit integers) is very annoying. 225 + Hopefully the unchecked conversions won't haunt me later.</p> 226 + <p><img src="https://cdn.oppi.li/L4.mp4" alt="Day 2" /></p> 227 + <h3 id="day-1">Day 1</h3> 228 + <p>Getting started with Rust and SDL2 is very straightforward. 229 + The <code>rust-sdl2</code> library contains some detailed examples that 230 + allowed me to get all the way to drawing a grid from a 231 + <code>Vec&lt;bool&gt;</code>:</p> 232 + <p><img src="https://cdn.oppi.li/Ma.png" alt="Day 1" /></p> 233 + </div></main></div></body></html>
+36
_site/posts/WPA_woes/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>WPA woes</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">WPA woes</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">WPA woes</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>16 sentences</span><span>·</span><span>23.37 cm</span><span>·</span><span>12.10.2019</span></div><p>I finally got around to installing Void GNU/Linux on my main 3 + computer. Rolling release, non-systemd, need I say more?</p> 4 + <p>As with all GNU/Linux distributions, wireless networks had 5 + me in a fix. If you can see this post, it means I've managed 6 + to get online. It turns out, <code>wpa_supplicant</code> was detecting the 7 + wrong interface by default (does it ever select the right 8 + one?). Let us fix that:</p> 9 + <pre><code>$ sudo rm -r /var/service/wpa_supplicant 10 + $ sudo killall dhcpcd 11 + </code></pre> 12 + <p>What is the right interface though?</p> 13 + <pre><code>$ iw dev 14 + ... 15 + Interface wlp2s0 16 + ... 17 + </code></pre> 18 + <p>Aha! Let us run <code>wpa_supplicant</code> on that interface, as a 19 + background process:</p> 20 + <pre><code>$ sudo wpa_supplicant -B -i wlp2s0 -c /etc/wpa_supplicant/wpa_supplicant.conf 21 + $ sudo dhcpcd -B wlp2s0 22 + $ ping google.com 23 + PING ... 24 + </code></pre> 25 + <p>Yay! Make those changes perpetual by enabling the service:</p> 26 + <pre><code>------------------------------------------------------ 27 + # Add these to /etc/wpa_supplicant/wpa_supplicant.conf 28 + OPTS=&quot;-B&quot; 29 + WPA_INTERFACE=&quot;wlp2s0&quot; 30 + ------------------------------------------------------ 31 + $ sudo ln -s /etc/sv/wpa_supplicant /var/service/ 32 + $ sudo ln -s /etc/sv/dhcpcd /var/service/ 33 + $ sudo sv restart wpa_supplicant 34 + $ sudo sv restart dhcpcd 35 + </code></pre> 36 + </div></main></div></body></html>
+51
_site/posts/a_reference_counted_afterlife/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>a reference counted afterlife</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">a reference counted afterlife</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">a reference counted afterlife</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>14 sentences</span><span>·</span><span>24.89 cm</span><span>·</span><span>02.08.2022</span></div><p>I took interest in the Egyptian rendition of the afterlife 3 + recently.</p> 4 + <h3 id="parts-of-the-soul">Parts of the Soul</h3> 5 + <p>Ancient Egyptians believed that the soul comprised of 6 + several components:</p> 7 + <ul> 8 + <li><em>ren</em> 9 + </li> 10 + <li><em>ka</em> 11 + </li> 12 + <li><em>ib</em> 13 + </li> 14 + <li><em>ba</em> 15 + </li> 16 + <li><em>sheut</em> 17 + </li> 18 + </ul> 19 + <p>Egyptians emphasized on preserving the different parts of 20 + the soul. Mummification for example, served to preserve the 21 + physical part of the soul. The other components have their 22 + respective preservation strategies.</p> 23 + <p>Of all of these bits, I find <em>ren</em>, which simply means 24 + <em>name</em>, to be the most interesting. <em>Ba</em>, the human-headed 25 + chicken that represents <em>personality</em>, is a close favourite.</p> 26 + <p><em>Ren</em> is the name given to a person at birth. Egyptians 27 + believed that this portion of the soul would continue to 28 + live on for as long as it was spoken. If you were someone 29 + worthy of continued existence, your name would be inscribed 30 + all over the place. If you were the type to snatch away 31 + bread from children, your name would be condemned from 32 + memory, forgotten.</p> 33 + <h3 id="garbage-collection">Garbage-collection</h3> 34 + <p>The concept of <em>ren</em> seems to be perfectly analogous to 35 + reference counted garbage-collection.</p> 36 + <ul> 37 + <li>A name (<em>ren</em>) is assigned to an object (person) on 38 + initialization (at birth) 39 + </li> 40 + <li>Names are used to refer to objects 41 + </li> 42 + <li>Objects go out of existence when there are no more 43 + references to them 44 + </li> 45 + </ul> 46 + <p>The concept of <em>ren</em> seems to model human-memory. The 47 + similarity with garbage-collection is now easily explained, 48 + because garbage-collection models a program's memory.</p> 49 + <p>Perhaps some cheeky Egyptian has attained immortality by 50 + creating a <em>ren</em>-cycle.</p> 51 + </div></main></div></body></html>
+735
_site/posts/auto-currying_rust_functions/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>auto-currying rust functions</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">auto-currying rust functions</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">auto-currying rust functions</h1><div class="text-center space-x-2"><span>18 min</span><span>·</span><span>360 sentences</span><span>·</span><span>458.24 cm</span><span>·</span><span>08.05.2020</span></div><p>This post contains a gentle introduction to procedural 3 + macros in Rust and a guide to writing a procedural macro to 4 + curry Rust functions. The source code for the entire library 5 + can be found <a href="https://github.com/nerdypepper/cutlass">here</a>. 6 + It is also available on <a href="https://crates.io/crates/cutlass">crates.io</a>.</p> 7 + <p>The following links might prove to be useful before getting 8 + started:</p> 9 + <ul> 10 + <li><a href="https://doc.rust-lang.org/reference/procedural-macros.html">Procedural Macros</a> 11 + </li> 12 + <li><a href="https://en.wikipedia.org/wiki/Currying">Currying</a> 13 + </li> 14 + </ul> 15 + <p>Or you can pretend you read them, because I have included 16 + a primer here :)</p> 17 + <h3 id="contents">Contents</h3> 18 + <ol> 19 + <li><a href="#currying">Currying</a> 20 + </li> 21 + <li><a href="#procedural-macros">Procedural Macros</a> 22 + </li> 23 + <li><a href="#definitions">Definitions</a> 24 + </li> 25 + <li><a href="#refinement">Refinement</a> 26 + </li> 27 + <li><a href="#the-in-betweens">The In-betweens</a><br /> 28 +      5.1 <a href="#dependencies">Dependencies</a><br /> 29 +      5.2 <a href="#the-attribute-macro">The attribute macro</a><br /> 30 +      5.3 <a href="#function-body">Function Body</a><br /> 31 +      5.4 <a href="#function-signature">Function Signature</a><br /> 32 +      5.5 <a href="#getting-it-together">Getting it together</a> 33 + </li> 34 + <li><a href="#debugging-and-testing">Debugging and Testing</a> 35 + </li> 36 + <li><a href="#notes">Notes</a> 37 + </li> 38 + <li><a href="#conclusion">Conclusion</a> 39 + </li> 40 + </ol> 41 + <h3 id="currying">Currying</h3> 42 + <p>Currying is the process of transformation of a function call 43 + like <code>f(a, b, c)</code> to <code>f(a)(b)(c)</code>. A curried function 44 + returns a concrete value only when it receives all its 45 + arguments! If it does recieve an insufficient amount of 46 + arguments, say 1 of 3, it returns a <em>curried function</em>, that 47 + returns after receiving 2 arguments.</p> 48 + <pre><code>curry(f(a, b, c)) = h(a)(b)(c) 49 + 50 + h(x) = g &lt;- curried function that takes upto 2 args (g) 51 + g(y) = k &lt;- curried function that takes upto 1 arg (k) 52 + k(z) = v &lt;- a value (v) 53 + 54 + Keen readers will conclude the following, 55 + h(x)(y)(z) = g(y)(z) = k(z) = v 56 + </code></pre> 57 + <p>Mathematically, if <code>f</code> is a function that takes two 58 + arguments <code>x</code> and <code>y</code>, such that <code>x ϵ X</code>, and <code>y ϵ Y</code> , we 59 + write it as:</p> 60 + <pre><code>f: (X × Y) -&gt; Z 61 + </code></pre> 62 + <p>where <code>×</code> denotes the Cartesian product of set <code>X</code> and <code>Y</code>, 63 + and curried <code>f</code> (denoted by <code>h</code> here) is written as:</p> 64 + <pre><code>h: X -&gt; (Y -&gt; Z) 65 + </code></pre> 66 + <h3 id="procedural-macros">Procedural Macros</h3> 67 + <p>These are functions that take code as input and spit out 68 + modified code as output. Powerful stuff. Rust has three 69 + kinds of proc-macros:</p> 70 + <ul> 71 + <li>Function like macros 72 + </li> 73 + <li>Derive macros: <code>#[derive(...)]</code>, used to automatically 74 + implement traits for structs/enums 75 + </li> 76 + <li>and Attribute macros: <code>#[test]</code>, usually slapped onto 77 + functions 78 + </li> 79 + </ul> 80 + <p>We will be using Attribute macros to convert a Rust function 81 + into a curried Rust function, which we should be able to 82 + call via: <code>function(arg1)(arg2)</code>.</p> 83 + <h3 id="definitions">Definitions</h3> 84 + <p>Being respectable programmers, we define the input to and 85 + the output from our proc-macro. Here's a good non-trivial 86 + function to start out with:</p> 87 + <pre><code class="language-rust">fn add(x: u32, y: u32, z: u32) -&gt; u32 { 88 + return x + y + z; 89 + } 90 + </code></pre> 91 + <p>Hmm, what would our output look like? What should our 92 + proc-macro generate ideally? Well, if we understood currying 93 + correctly, we should accept an argument and return a 94 + function that accepts an argument and returns ... you get 95 + the point. Something like this should do:</p> 96 + <pre><code class="language-rust">fn add_curried1(x: u32) -&gt; ? { 97 + return fn add_curried2 (y: u32) -&gt; ? { 98 + return fn add_curried3 (z: u32) -&gt; u32 { 99 + return x + y + z; 100 + } 101 + } 102 + } 103 + </code></pre> 104 + <p>A couple of things to note:</p> 105 + <p><strong>Return types</strong><br /> 106 + We have placed <code>?</code>s in place of return 107 + types. Let's try to fix that. <code>add_curried3</code> returns the 108 + 'value', so <code>u32</code> is accurate. <code>add_curried2</code> returns 109 + <code>add_curried3</code>. What is the type of <code>add_curried3</code>? It is a 110 + function that takes in a <code>u32</code> and returns a <code>u32</code>. So a 111 + <code>fn(u32) -&gt; u32</code> will do right? No, I'll explain why in the 112 + next point, but for now, we will make use of the <code>Fn</code> trait, 113 + our return type is <code>impl Fn(u32) -&gt; u32</code>. This basically 114 + tells the compiler that we will be returning something 115 + function-like, a.k.a, behaves like a <code>Fn</code>. Cool!</p> 116 + <p>If you have been following along, you should be able to tell 117 + that the return type of <code>add_curried1</code> is:</p> 118 + <pre><code>impl Fn(u32) -&gt; (impl Fn(u32) -&gt; u32) 119 + </code></pre> 120 + <p>We can drop the parentheses because <code>-&gt;</code> is right associative:</p> 121 + <pre><code>impl Fn(u32) -&gt; impl Fn(u32) -&gt; u32 122 + 123 + </code></pre> 124 + <p><strong>Accessing environment</strong><br /> 125 + A function cannot access it's environment. Our solution 126 + will not work. <code>add_curried3</code> attempts to access <code>x</code>, which 127 + is not allowed! A closure<a href="%5Bhttps://doc.rust-lang.org/book/ch13-01-closures.html%5D(https://doc.rust-lang.org/book/ch13-01-closures.html)">^closure</a> however, can. If we are 128 + returning a closure, our return type must be <code>impl Fn</code>, and 129 + not <code>fn</code>. The difference between the <code>Fn</code> trait and 130 + function pointers is beyond the scope of this post.</p> 131 + <h3 id="refinement">Refinement</h3> 132 + <p>Armed with knowledge, we refine our expected output, this 133 + time, employing closures:</p> 134 + <pre><code class="language-rust">fn add(x: u32) -&gt; impl Fn(u32) -&gt; impl Fn(u32) -&gt; u32 { 135 + return move |y| move |z| x + y + z; 136 + } 137 + </code></pre> 138 + <p>Alas, that does not compile either! It errors out with the 139 + following message:</p> 140 + <pre><code>error[E0562]: `impl Trait` not allowed outside of function 141 + and inherent method return types 142 + --&gt; src/main.rs:17:37 143 + | 144 + | fn add(x: u32) -&gt; impl Fn(u32) -&gt; impl Fn(u32) -&gt; u32 145 + | ^^^^^^^^^^^^^^^^^^^ 146 + 147 + </code></pre> 148 + <p>You are allowed to return an <code>impl Fn</code> only inside a 149 + function. We are currently returning it from another return! 150 + Or at least, that was the most I could make out of the error 151 + message.</p> 152 + <p>We are going to have to cheat a bit to fix this issue; with 153 + type aliases and a convenient nightly feature [^features]:</p> 154 + <p>[^features]: <a href="https://caniuse.rs">caniuse.rs</a> contains an 155 + indexed list of features and their status.</p> 156 + <pre><code class="language-rust">#![feature(type_alias_impl_trait)] // allows us to use `impl Fn` in type aliases! 157 + 158 + type T0 = u32; // the return value when zero args are to be applied 159 + type T1 = impl Fn(u32) -&gt; T0; // the return value when one arg is to be applied 160 + type T2 = impl Fn(u32) -&gt; T1; // the return value when two args are to be applied 161 + 162 + fn add(x: u32) -&gt; T2 { 163 + return move |y| move |z| x + y + z; 164 + } 165 + </code></pre> 166 + <p>Drop that into a cargo project, call <code>add(4)(5)(6)</code>, cross 167 + your fingers, and run <code>cargo +nightly run</code>. You should see a 168 + 15 unless you forgot to print it!</p> 169 + <h3 id="the-in-betweens">The In-Betweens</h3> 170 + <p>Let us write the magical bits that take us from function to 171 + curried function.</p> 172 + <p>Initialize your workspace with <code>cargo new --lib currying</code>. 173 + Proc-macro crates are libraries with exactly one export, the 174 + macro itself. Add a <code>tests</code> directory to your crate root. 175 + Your directory should look something like this:</p> 176 + <pre><code>. 177 + ├── Cargo.toml 178 + ├── src 179 + │   └── lib.rs 180 + └── tests 181 + └── smoke.rs 182 + </code></pre> 183 + <h4 id="dependencies">Dependencies</h4> 184 + <p>We will be using a total of 3 external crates:</p> 185 + <ul> 186 + <li><a href="https://docs.rs/proc-macro2/1.0.12/proc_macro2/">proc_macro2</a> 187 + </li> 188 + <li><a href="https://docs.rs/syn/1.0.18/syn/index.html">syn</a> 189 + </li> 190 + <li><a href="https://docs.rs/quote/1.0.4/quote/index.html">quote</a> 191 + </li> 192 + </ul> 193 + <p>Here's a sample <code>Cargo.toml</code>:</p> 194 + <pre><code># Cargo.toml 195 + 196 + [dependencies] 197 + proc-macro2 = &quot;1.0.9&quot; 198 + quote = &quot;1.0&quot; 199 + 200 + [dependencies.syn] 201 + version = &quot;1.0&quot; 202 + features = [&quot;full&quot;] 203 + 204 + [lib] 205 + proc-macro = true # this is important! 206 + </code></pre> 207 + <p>We will be using an external <code>proc-macro2</code> crate as well as 208 + an internal <code>proc-macro</code> crate. Not confusing at all!</p> 209 + <h4 id="the-attribute-macro">The attribute macro</h4> 210 + <p>Drop this into <code>src/lib.rs</code>, to get the ball rolling.</p> 211 + <pre><code class="language-rust">// src/lib.rs 212 + 213 + use proc_macro::TokenStream; // 1 214 + use quote::quote; 215 + use syn::{parse_macro_input, ItemFn}; 216 + 217 + #[proc_macro_attribute] // 2 218 + pub fn curry(_attr: TokenStream, item: TokenStream) -&gt; TokenStream { 219 + let parsed = parse_macro_input!(item as ItemFn); // 3 220 + generate_curry(parsed).into() // 4 221 + } 222 + 223 + fn generate_curry(parsed: ItemFn) -&gt; proc_macro2::TokenStream {} 224 + </code></pre> 225 + <p><strong>1. Imports</strong></p> 226 + <p>A <code>Tokenstream</code> holds (hopefully valid) Rust code, this 227 + is the type of our input and output. Note that we are 228 + importing this type from <code>proc_macro</code> and not <code>proc_macro2</code>.</p> 229 + <p><code>quote!</code> from the <code>quote</code> crate is a macro that allows us to 230 + quickly produce <code>TokenStream</code>s. Much like the LISP <code>quote</code> 231 + procedure, you can use the <code>quote!</code> macro for symbolic 232 + transformations.</p> 233 + <p><code>ItemFn</code> from the <code>syn</code> crate holds the parsed <code>TokenStream</code> 234 + of a Rust function. <code>parse_macro_input!</code> is a helper macro 235 + provided by <code>syn</code>.</p> 236 + <p><strong>2. The lone export</strong></p> 237 + <p>Annotate the only <code>pub</code> of our crate with 238 + <code>#[proc_macro_attribute]</code>. This tells rustc that <code>curry</code> is 239 + a procedural macro, and allows us to use it as 240 + <code>#[crate_name::curry]</code> in other crates. Note the signature 241 + of the <code>curry</code> function. <code>_attr</code> is the <code>TokenStream</code> 242 + representing the attribute itself, <code>item</code> refers to the 243 + thing we slapped our macro into, in this case a function 244 + (like <code>add</code>). The return value is a modified <code>TokenStream</code>, 245 + this will contain our curried version of <code>add</code>.</p> 246 + <p><strong>3. The helper macro</strong></p> 247 + <p>A <code>TokenStream</code> is a little hard to work with, which is why 248 + we have the <code>syn</code> crate, which provides types to represent 249 + Rust tokens. An <code>RArrow</code> struct to represent the return 250 + arrow on a function and so on. One of those types is 251 + <code>ItemFn</code>, that represents an entire Rust function. The 252 + <code>parse_macro_input!</code> automatically puts the input to our 253 + macro into an <code>ItemFn</code>. What a gentleman!</p> 254 + <p>**4. Returning <code>TokenStream</code>s **</p> 255 + <p>We haven't filled in <code>generate_curry</code> yet, but we can see 256 + that it returns a <code>proc_macro2::TokenStream</code> and not a 257 + <code>proc_macro::TokenStream</code>, so drop a <code>.into()</code> to convert 258 + it.</p> 259 + <p>Lets move on, and fill in <code>generate_curry</code>, I would suggest 260 + keeping the documentation for 261 + <a href="https://docs.rs/syn/1.0.19/syn/struct.ItemFn.html"><code>syn::ItemFn</code></a> 262 + and 263 + <a href="https://docs.rs/syn/1.0.19/syn/struct.Signature.html"><code>syn::Signature</code></a> 264 + open.</p> 265 + <pre><code class="language-rust">// src/lib.rs 266 + 267 + fn generate_curry(parsed: ItemFn) -&gt; proc_macro2::TokenStream { 268 + let fn_body = parsed.block; // function body 269 + let sig = parsed.sig; // function signature 270 + let vis = parsed.vis; // visibility, pub or not 271 + let fn_name = sig.ident; // function name/identifier 272 + let fn_args = sig.inputs; // comma separated args 273 + let fn_return_type = sig.output; // return type 274 + } 275 + </code></pre> 276 + <p>We are simply extracting the bits of the function, we will 277 + be reusing the original function's visibility and name. Take 278 + a look at what <code>syn::Signature</code> can tell us about a 279 + function:</p> 280 + <pre><code> .-- syn::Ident (ident) 281 + / 282 + fn add(x: u32, y: u32) -&gt; u32 283 + (fn_token) / ~~~~~~~,~~~~~~ ~~~~~~ 284 + syn::token::Fn --' / \ (output) 285 + ' `- syn::ReturnType 286 + Punctuated&lt;FnArg, Comma&gt; (inputs) 287 + </code></pre> 288 + <p>Enough analysis, lets produce our first bit of Rust code.</p> 289 + <h4 id="function-body">Function Body</h4> 290 + <p>Recall that the body of a curried <code>add</code> should look like 291 + this:</p> 292 + <pre><code class="language-rust">return move |y| move |z| x + y + z; 293 + </code></pre> 294 + <p>And in general:</p> 295 + <pre><code class="language-rust">return move |arg2| move |arg3| ... |argN| &lt;function body here&gt; 296 + </code></pre> 297 + <p>We already have the function's body, provided by <code>fn_body</code>, 298 + in our <code>generate_curry</code> function. All that's left to add is 299 + the <code>move |arg2| move |arg3| ...</code> stuff, for which we need 300 + to extract the argument identifiers 301 + (doc: 302 + <a href="https://docs.rs/syn/1.0.18/syn/punctuated/struct.Punctuated.html">Punctuated</a>, 303 + <a href="https://docs.rs/syn/1.0.18/syn/enum.FnArg.html">FnArg</a>, 304 + <a href="https://docs.rs/syn/1.0.18/syn/struct.PatType.html">PatType</a>):</p> 305 + <pre><code class="language-rust">// src/lib.rs 306 + use syn::punctuated::Punctuated; 307 + use syn::{parse_macro_input, FnArg, Pat, ItemFn, Block}; 308 + 309 + fn extract_arg_idents(fn_args: Punctuated&lt;FnArg, syn::token::Comma&gt;) -&gt; Vec&lt;Box&lt;Pat&gt;&gt; { 310 + return fn_args.into_iter().map(extract_arg_pat).collect::&lt;Vec&lt;_&gt;&gt;(); 311 + } 312 + </code></pre> 313 + <p>Alright, so we are iterating over function args 314 + (<code>Punctuated</code> is a collection that you can iterate over) and 315 + mapping an <code>extract_arg_pat</code> to every item. What's 316 + <code>extract_arg_pat</code>?</p> 317 + <pre><code class="language-rust">// src/lib.rs 318 + 319 + fn extract_arg_pat(a: FnArg) -&gt; Box&lt;Pat&gt; { 320 + match a { 321 + FnArg::Typed(p) =&gt; p.pat, 322 + _ =&gt; panic!(&quot;Not supported on types with `self`!&quot;), 323 + } 324 + } 325 + </code></pre> 326 + <p><code>FnArg</code> is an enum type as you might have guessed. The 327 + <code>Typed</code> variant encompasses args that are written as <code>name: type</code> and the other variant, <code>Reciever</code> refers to <code>self</code> 328 + types. Ignore those for now, keep it simple.</p> 329 + <p>Every <code>FnArg::Typed</code> value contains a <code>pat</code>, which is in 330 + essence, the name of the argument. The type of the arg is 331 + accessible via <code>p.ty</code> (we will be using this later).</p> 332 + <p>With that done, we should be able to write the codegen for 333 + the function body:</p> 334 + <pre><code class="language-rust">// src/lib.rs 335 + 336 + fn generate_body(fn_args: &amp;[Box&lt;Pat&gt;], body: Box&lt;Block&gt;) -&gt; proc_macro2::TokenStream { 337 + quote! { 338 + return #( move |#fn_args| )* #body 339 + } 340 + } 341 + </code></pre> 342 + <p>That is some scary looking syntax! Allow me to explain. The 343 + <code>quote!{ ... }</code> returns a <code>proc_macro2::TokenStream</code>, if we 344 + wrote <code>quote!{ let x = 1 + 2; }</code>, it wouldn't create a new 345 + variable <code>x</code> with value 3, it would literally produce a 346 + stream of tokens with that expression.</p> 347 + <p>The <code>#</code> enables variable interpolation. <code>#body</code> will look 348 + for <code>body</code> in the current scope, take its value, and insert 349 + it in the returned <code>TokenStream</code>. Kinda like quasi quoting 350 + in LISPs, you have written one.</p> 351 + <p>What about <code>#( move |#fn_args| )*</code>? That is repetition. 352 + <code>quote</code> iterates through <code>fn_args</code>, and drops a <code>move</code> behind 353 + each one, it then places pipes (<code>|</code>), around it.</p> 354 + <p>Let us test our first bit of codegen! Modify <code>generate_curry</code> like so:</p> 355 + <pre><code class="language-rust">// src/lib.rs 356 + 357 + fn generate_curry(parsed: ItemFn) -&gt; TokenStream { 358 + let fn_body = parsed.block; 359 + let sig = parsed.sig; 360 + let vis = parsed.vis; 361 + let fn_name = sig.ident; 362 + let fn_args = sig.inputs; 363 + let fn_return_type = sig.output; 364 + 365 + + let arg_idents = extract_arg_idents(fn_args.clone()); 366 + + let first_ident = &amp;arg_idents.first().unwrap(); 367 + 368 + + // remember, our curried body starts with the second argument! 369 + + let curried_body = generate_body(&amp;arg_idents[1..], fn_body.clone()); 370 + + println!(&quot;{}&quot;, curried_body); 371 + 372 + return TokenStream::new(); 373 + } 374 + </code></pre> 375 + <p>Add a little test to <code>tests/</code>:</p> 376 + <pre><code class="language-rust">// tests/smoke.rs 377 + 378 + #[currying::curry] 379 + fn add(x: u32, y: u32, z: u32) -&gt; u32 { 380 + x + y + z 381 + } 382 + 383 + #[test] 384 + fn works() { 385 + assert!(true); 386 + } 387 + </code></pre> 388 + <p>You should find something like this in the output of <code>cargo test</code>:</p> 389 + <pre><code>return move | y | move | z | { x + y + z } 390 + </code></pre> 391 + <p>Glorious <code>println!</code> debugging!</p> 392 + <h4 id="function-signature">Function signature</h4> 393 + <p>This section gets into the more complicated bits of the 394 + macro, generating type aliases and the function signature. 395 + By the end of this section, we should have a full working 396 + auto-currying macro!</p> 397 + <p>Recall what our generated type aliases should look like, for 398 + our <code>add</code> function:</p> 399 + <pre><code class="language-rust">type T0 = u32; 400 + type T1 = impl Fn(u32) -&gt; T0; 401 + type T2 = impl Fn(u32) -&gt; T1; 402 + </code></pre> 403 + <p>In general:</p> 404 + <pre><code class="language-rust">type T0 = &lt;return type&gt;; 405 + type T1 = impl Fn(&lt;type of arg N&gt;) -&gt; T0; 406 + type T2 = impl Fn(&lt;type of arg N - 1&gt;) -&gt; T1; 407 + . 408 + . 409 + . 410 + type T(N-1) = impl Fn(&lt;type of arg 2&gt;) -&gt; T(N-2); 411 + </code></pre> 412 + <p>To codegen that, we need the types of:</p> 413 + <ul> 414 + <li>all our inputs (arguments) 415 + </li> 416 + <li>the output (the return type) 417 + </li> 418 + </ul> 419 + <p>To fetch the types of all our inputs, we can simply reuse 420 + the bits we wrote to fetch the names of all our inputs! 421 + (doc: <a href="https://docs.rs/syn/1.0.18/syn/enum.Type.html">Type</a>)</p> 422 + <pre><code class="language-rust">// src/lib.rs 423 + 424 + use syn::{parse_macro_input, Block, FnArg, ItemFn, Pat, ReturnType, Type}; 425 + 426 + fn extract_type(a: FnArg) -&gt; Box&lt;Type&gt; { 427 + match a { 428 + FnArg::Typed(p) =&gt; p.ty, // notice `ty` instead of `pat` 429 + _ =&gt; panic!(&quot;Not supported on types with `self`!&quot;), 430 + } 431 + } 432 + 433 + fn extract_arg_types(fn_args: Punctuated&lt;FnArg, syn::token::Comma&gt;) -&gt; Vec&lt;Box&lt;Type&gt;&gt; { 434 + return fn_args.into_iter().map(extract_type).collect::&lt;Vec&lt;_&gt;&gt;(); 435 + 436 + } 437 + </code></pre> 438 + <p>A good reader would have looked at the docs for output 439 + member of the <code>syn::Signature</code> struct. It has the type 440 + <code>syn::ReturnType</code>. So there is no extraction to do here 441 + right? There are actually a couple of things we have to 442 + ensure here:</p> 443 + <ol> 444 + <li> 445 + <p>We need to ensure that the function returns! A function 446 + that does not return is pointless in this case, and I 447 + will tell you why, in the <a href="#notes">Notes</a> section.</p> 448 + </li> 449 + <li> 450 + <p>A <code>ReturnType</code> encloses the arrow of the return as well, 451 + we need to get rid of that. Recall:</p> 452 + <pre><code class="language-rust"></code></pre> 453 + </li> 454 + </ol> 455 + <p>type T0 = u32 456 + // and not 457 + type T0 = -&gt; u32</p> 458 + <pre><code> 459 + Here is the snippet that handles extraction of the 460 + return type (doc: [syn::ReturnType](https://docs.rs/syn/1.0.19/syn/enum.ReturnType.html)): 461 + 462 + ```rust 463 + // src/lib.rs 464 + 465 + fn extract_return_type(a: ReturnType) -&gt; Box&lt;Type&gt; { 466 + match a { 467 + ReturnType::Type(_, p) =&gt; p, 468 + _ =&gt; panic!(&quot;Not supported on functions without return types!&quot;), 469 + } 470 + } 471 + </code></pre> 472 + <p>You might notice that we are making extensive use of the 473 + <code>panic!</code> macro. Well, that is because it is a good idea to 474 + quit on receiving an unsatisfactory <code>TokenStream</code>.</p> 475 + <p>With all our types ready, we can get on with generating type 476 + aliases:</p> 477 + <pre><code class="language-rust">// src/lib.rs 478 + 479 + use quote::{quote, format_ident}; 480 + 481 + fn generate_type_aliases( 482 + fn_arg_types: &amp;[Box&lt;Type&gt;], 483 + fn_return_type: Box&lt;Type&gt;, 484 + fn_name: &amp;syn::Ident, 485 + ) -&gt; Vec&lt;proc_macro2::TokenStream&gt; { // 1 486 + 487 + let type_t0 = format_ident!(&quot;_{}_T0&quot;, fn_name); // 2 488 + let mut type_aliases = vec![quote! { type #type_t0 = #fn_return_type }]; 489 + 490 + // 3 491 + for (i, t) in (1..).zip(fn_arg_types.into_iter().rev()) { 492 + let p = format_ident!(&quot;_{}_{}&quot;, fn_name, format!(&quot;T{}&quot;, i - 1)); 493 + let n = format_ident!(&quot;_{}_{}&quot;, fn_name, format!(&quot;T{}&quot;, i)); 494 + 495 + type_aliases.push(quote! { 496 + type #n = impl Fn(#t) -&gt; #p 497 + }); 498 + } 499 + 500 + return type_aliases; 501 + } 502 + 503 + </code></pre> 504 + <p><strong>1. The return value</strong><br /> 505 + We are returning a <code>Vec&lt;proc_macro2::TokenStream&gt;</code>, i. e., a 506 + list of <code>TokenStream</code>s, where each item is a type alias.</p> 507 + <p><strong>2. Format identifier?</strong><br /> 508 + I've got some explanation to do on this line. Clearly, we 509 + are trying to write the first type alias, and initialize our 510 + <code>TokenStream</code> vector with <code>T0</code>, because it is different from 511 + the others:</p> 512 + <pre><code class="language-rust">type T0 = something 513 + // the others are of the form 514 + type Tr = impl Fn(something) -&gt; something 515 + </code></pre> 516 + <p><code>format_ident!</code> is similar to <code>format!</code>. Instead of 517 + returning a formatted string, it returns a <code>syn::Ident</code>. 518 + Therefore, <code>type_t0</code> is actually an identifier for, in the 519 + case of our <code>add</code> function, <code>_add_T0</code>. Why is this 520 + formatting important? Namespacing.</p> 521 + <p>Picture this, we have two functions, <code>add</code> and <code>subtract</code>, 522 + that we wish to curry with our macro:</p> 523 + <pre><code class="language-rust">#[curry] 524 + fn add(...) -&gt; u32 { ... } 525 + 526 + #[curry] 527 + fn sub(...) -&gt; u32 { ... } 528 + </code></pre> 529 + <p>Here is the same but with macros expanded:</p> 530 + <pre><code class="language-rust">type T0 = u32; 531 + type T1 = impl Fn(u32) -&gt; T0; 532 + fn add( ... ) -&gt; T1 { ... } 533 + 534 + type T0 = u32; 535 + type T1 = impl Fn(u32) -&gt; T0; 536 + fn sub( ... ) -&gt; T1 { ... } 537 + </code></pre> 538 + <p>We end up with two definitions of <code>T0</code>! Now, if we do the 539 + little <code>format_ident!</code> dance we did up there:</p> 540 + <pre><code class="language-rust">type _add_T0 = u32; 541 + type _add_T1 = impl Fn(u32) -&gt; _add_T0; 542 + fn add( ... ) -&gt; _add_T1 { ... } 543 + 544 + type _sub_T0 = u32; 545 + type _sub_T1 = impl Fn(u32) -&gt; _sub_T0; 546 + fn sub( ... ) -&gt; _sub_T1 { ... } 547 + </code></pre> 548 + <p>Voilà! The type aliases don't tread on each other. Remember 549 + to import <code>format_ident</code> from the <code>quote</code> crate.</p> 550 + <p><strong>3. The TokenStream Vector</strong></p> 551 + <p>We iterate over our types in reverse order (<code>T0</code> is the 552 + last return, <code>T1</code> is the second last, so on), assign a 553 + number to each iteration with <code>zip</code>, generate type names 554 + with <code>format_ident</code>, push a <code>TokenStream</code> with the help of 555 + <code>quote</code> and variable interpolation.</p> 556 + <p>If you are wondering why we used <code>(1..).zip()</code> instead of 557 + <code>.enumerate()</code>, it's because we wanted to start counting 558 + from 1 instead of 0 (we are already done with <code>T0</code>!).</p> 559 + <h4 id="getting-it-together">Getting it together</h4> 560 + <p>I promised we'd have a fully working macro by the end of 561 + last section. I lied, we have to tie everything together in 562 + our <code>generate_curry</code> function:</p> 563 + <pre><code class="language-rust">// src/lib.rs 564 + 565 + fn generate_curry(parsed: ItemFn) -&gt; proc_macro2::TokenStream { 566 + let fn_body = parsed.block; 567 + let sig = parsed.sig; 568 + let vis = parsed.vis; 569 + let fn_name = sig.ident; 570 + let fn_args = sig.inputs; 571 + let fn_return_type = sig.output; 572 + 573 + let arg_idents = extract_arg_idents(fn_args.clone()); 574 + let first_ident = &amp;arg_idents.first().unwrap(); 575 + let curried_body = generate_body(&amp;arg_idents[1..], fn_body.clone()); 576 + 577 + + let arg_types = extract_arg_types(fn_args.clone()); 578 + + let first_type = &amp;arg_types.first().unwrap(); 579 + + let type_aliases = generate_type_aliases( 580 + + &amp;arg_types[1..], 581 + + extract_return_type(fn_return_type), 582 + + &amp;fn_name, 583 + + ); 584 + 585 + + let return_type = format_ident!(&quot;_{}_{}&quot;, &amp;fn_name, format!(&quot;T{}&quot;, type_aliases.len() - 1)); 586 + 587 + + return quote! { 588 + + #(#type_aliases);* ; 589 + + #vis fn #fn_name (#first_ident: #first_type) -&gt; #return_type { 590 + + #curried_body ; 591 + + } 592 + + }; 593 + } 594 + </code></pre> 595 + <p>Most of the additions are self explanatory, I'll go through 596 + the return statement with you. We are returning a <code>quote!{ ... }</code>, so a <code>proc_macro2::TokenStream</code>. We are iterating 597 + through the <code>type_aliases</code> variable, which you might recall, 598 + is a <code>Vec&lt;TokenStream&gt;</code>. You might notice the sneaky 599 + semicolon before the <code>*</code>. This basically tells <code>quote</code>, to 600 + insert an item, then a semicolon, and then the next one, 601 + another semicolon, and so on. The semicolon is a separator. 602 + We need to manually insert another semicolon at the end of 603 + it all, <code>quote</code> doesn't insert a separator at the end of the 604 + iteration.</p> 605 + <p>We retain the visibility and name of our original function. 606 + Our curried function takes as args, just the first argument 607 + of our original function. The return type of our curried 608 + function is actually, the last type alias we create. If you 609 + think back to our manually curried <code>add</code> function, we 610 + returned <code>T2</code>, which was in fact, the last type alias we 611 + created.</p> 612 + <p>I am sure, at this point, you are itching to test this out, 613 + but before that, let me introduce you to some good methods 614 + of debugging proc-macro code.</p> 615 + <h3 id="debugging-and-testing">Debugging and Testing</h3> 616 + <p>Install <code>cargo-expand</code> via:</p> 617 + <pre><code>cargo install cargo-expand 618 + </code></pre> 619 + <p><code>cargo-expand</code> is a neat little tool that expands your macro 620 + in places where it is used, and lets you view the generated 621 + code! For example:</p> 622 + <pre><code class="language-shell"># create a bin package hello 623 + $ cargo new hello 624 + 625 + # view the expansion of the println! macro 626 + $ cargo expand 627 + 628 + #![feature(prelude_import)] 629 + #[prelude_import] 630 + use std::prelude::v1::*; 631 + #[macro_use] 632 + extern crate std; 633 + fn main() { 634 + { 635 + ::std::io::_print(::core::fmt::Arguments::new_v1( 636 + &amp;[&quot;Hello, world!\n&quot;], 637 + &amp;match () { 638 + () =&gt; [], 639 + }, 640 + )); 641 + }; 642 + } 643 + </code></pre> 644 + <p>Writing proc-macros without <code>cargo-expand</code> is tantamount to 645 + driving a vehicle without rear view mirrors! Keep an eye on 646 + what is going on behind your back.</p> 647 + <p>Now, your macro won't always compile, you might just recieve 648 + the bee movie script as an error. <code>cargo-expand</code> will not 649 + work in such cases. I would suggest printing out your 650 + variables to inspect them. <code>TokenStream</code> implements 651 + <code>Display</code> as well as <code>Debug</code>. We don't always have to be 652 + respectable programmers. Just print it.</p> 653 + <p>Enough of that, lets get testing:</p> 654 + <pre><code class="language-rust">// tests/smoke.rs 655 + 656 + #![feature(type_alias_impl_trait)] 657 + 658 + #[crate_name::curry] 659 + fn add(x: u32, y: u32, z: u32) -&gt; u32 { 660 + x + y + z 661 + } 662 + 663 + #[test] 664 + fn works() { 665 + assert_eq!(15, add(4)(5)(6)); 666 + } 667 + </code></pre> 668 + <p>Run <code>cargo +nightly test</code>. You should see a pleasing 669 + message:</p> 670 + <pre><code>running 1 test 671 + test tests::works ... ok 672 + </code></pre> 673 + <p>Take a look at the expansion for our curry macro, via 674 + <code>cargo +nightly expand --tests smoke</code>:</p> 675 + <pre><code class="language-rust">type _add_T0 = u32; 676 + type _add_T1 = impl Fn(u32) -&gt; _add_T0; 677 + type _add_T2 = impl Fn(u32) -&gt; _add_T1; 678 + fn add(x: u32) -&gt; _add_T2 { 679 + return (move |y| { 680 + move |z| { 681 + return x + y + z; 682 + } 683 + }); 684 + } 685 + 686 + // a bunch of other stuff generated by #[test] and assert_eq! 687 + </code></pre> 688 + <p>A sight for sore eyes.</p> 689 + <p>Here is a more complex example that generates ten multiples 690 + of the first ten natural numbers:</p> 691 + <pre><code class="language-rust">#[curry] 692 + fn product(x: u32, y: u32) -&gt; u32 { 693 + x * y 694 + } 695 + 696 + fn multiples() -&gt; Vec&lt;Vec&lt;u32&gt;&gt;{ 697 + let v = (1..=10).map(product); 698 + return (1..=10) 699 + .map(|x| v.clone().map(|f| f(x)).collect()) 700 + .collect(); 701 + } 702 + </code></pre> 703 + <h3 id="notes">Notes</h3> 704 + <p>I didn't quite explain why we use <code>move |arg|</code> in our 705 + closure. This is because we want to take ownership of the 706 + variable supplied to us. Take a look at this example:</p> 707 + <pre><code class="language-rust">let v = add(5); 708 + let g; 709 + { 710 + let x = 5; 711 + g = v(x); 712 + } 713 + println!(&quot;{}&quot;, g(2)); 714 + </code></pre> 715 + <p>Variable <code>x</code> goes out of scope before <code>g</code> can return a 716 + concrete value. If we take ownership of <code>x</code> by <code>move</code>ing it 717 + into our closure, we can expect this to work reliably. In 718 + fact, rustc understands this, and forces you to use <code>move</code>.</p> 719 + <p>This usage of <code>move</code> is exactly why <strong>a curried function 720 + without a return is useless</strong>. Every variable we pass to our 721 + curried function gets moved into its local scope. Playing 722 + with these variables cannot cause a change outside this 723 + scope. Returning is our only method of interaction with 724 + anything beyond this function.</p> 725 + <h3 id="conclusion">Conclusion</h3> 726 + <p>Currying may not seem to be all that useful. Curried 727 + functions are unwieldy in Rust because the standard library 728 + is not built around currying. If you enjoy the possibilities 729 + posed by currying, consider taking a look at Haskell or 730 + Scheme.</p> 731 + <p>My original intention with <a href="https://peppe.rs">peppe.rs</a> was 732 + to post condensed articles, a micro blog, but this one 733 + turned out extra long.</p> 734 + <p>Perhaps I should call it a 'macro' blog :)</p> 735 + </div></main></div></body></html>
+51
_site/posts/bash_harder_with_vim/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>bash harder with vim</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">bash harder with vim</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">bash harder with vim</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>12 sentences</span><span>·</span><span>32.01 cm</span><span>·</span><span>30.07.2019</span></div><p>Bash is tricky, don't let your editor get in your way. Here's a couple of neat 3 + additions you could make to your <code>vimrc</code> for a better shell programming 4 + experience.</p> 5 + <h3 id="man-pages-inside-vim">Man pages inside vim</h3> 6 + <p>Source this script to get started:</p> 7 + <pre><code>runtime ftplugin/man.vim 8 + </code></pre> 9 + <p>Now, you can open manpages inside vim with <code>:Man</code>! It adds nicer syntax highlighting 10 + and the ability to jump around with <code>Ctrl-]</code> and <code>Ctrl-T</code>.</p> 11 + <p>By default, the manpage is opened in a horizontal split, I prefer using a new tab:</p> 12 + <pre><code>let g:ft_man_open_mode = 'tab' 13 + </code></pre> 14 + <h3 id="scratchpad-to-test-your-commands">Scratchpad to test your commands</h3> 15 + <p>I often test my <code>sed</code> substitutions, here is 16 + a sample from the script used to generate this site:</p> 17 + <pre><code># a substitution to convert snake_case to Title Case With Spaces 18 + echo &quot;$1&quot; | sed -E -e &quot;s/\..+$//g&quot; -e &quot;s/_(.)/ \u\1/g&quot; -e &quot;s/^(.)/\u\1/g&quot; 19 + </code></pre> 20 + <p>Instead of dropping into a new shell, just test it out directly from vim!</p> 21 + <ul> 22 + <li>Yank the line into a register: 23 + </li> 24 + </ul> 25 + <pre><code>yy 26 + </code></pre> 27 + <ul> 28 + <li>Paste it into the command-line window: 29 + </li> 30 + </ul> 31 + <pre><code>q:p 32 + </code></pre> 33 + <ul> 34 + <li>Make edits as required: 35 + </li> 36 + </ul> 37 + <pre><code>syntax off # previously run commands 38 + edit index.html # in a buffer! 39 + w | so % 40 + !echo &quot;new_post.md&quot; | sed -E -e &quot;s/\..+$//g&quot; --snip-- 41 + ^--- note the use of '!' 42 + </code></pre> 43 + <ul> 44 + <li>Hit enter with the cursor on the line containing your command! 45 + </li> 46 + </ul> 47 + <pre><code>$ vim 48 + New Post # output 49 + Press ENTER or type command to continue 50 + </code></pre> 51 + </div></main></div></body></html>
+22
_site/posts/bye_bye_BDFs/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>bye bye BDFs</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">bye bye BDFs</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">bye bye BDFs</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>20 sentences</span><span>·</span><span>12.70 cm</span><span>·</span><span>07.08.2019</span></div><p>Glyph Bitmap Distribution Format is no more, as the creators of 3 + <a href="https://pango.org">Pango</a>, one of the most widely used text rendering 4 + libraries, 5 + <a href="https://blogs.gnome.org/mclasen/2019/05/25/pango-future-directions/">announced</a> 6 + their plans for Pango 1.44.</p> 7 + <p>Until recently, Pango used FreeType to draw fonts. They will be moving over 8 + to <a href="https://harfbuzz.org">Harfbuzz</a>, an evolution of FreeType.</p> 9 + <p><em>Why?</em></p> 10 + <p>In short, FreeType was hard to work with. It required complex logic, and 11 + provided no advantage over Harfbuzz (other than being able to fetch 12 + opentype metrics with ease).</p> 13 + <p>Upgrading to Pango v1.44 will break your GTK applications (if you use a 14 + <code>bdf</code>/<code>pcf</code> bitmap font). Harfbuzz <em>does</em> support bitmap-only OpenType fonts, 15 + <code>otb</code>s. Convert your existing fonts over to <code>otb</code>s using 16 + <a href="https://fontforge.github.io">FontForge</a>. It is to be noted that applications 17 + such as <code>xterm</code> and <code>rxvt</code> use <code>xft</code> (X FreeType) to render fonts, and will 18 + remain unaffected by the update.</p> 19 + <p>Both <a href="https://github.com/nerdypepper/scientifica">scientifica</a> and 20 + <a href="https://github.com/nerdypepper/curie">curie</a> will soon ship with bitmap-only 21 + OpenType font formats.</p> 22 + </div></main></div></body></html>
+63
_site/posts/call_to_ARMs/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>call to ARMs</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">call to ARMs</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">call to ARMs</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>36 sentences</span><span>·</span><span>42.67 cm</span><span>·</span><span>07.02.2020</span></div><p>My 4th semester involves ARM programming. And proprietary 3 + tooling (Keil C). But we don't do that here.</p> 4 + <h3 id="building">Building</h3> 5 + <p>Assembling and linking ARM binaries on non-ARM architecture 6 + devices is fairly trivial. I went along with the GNU cross 7 + bare metal toolchain binutils, which provides <code>arm-as</code> and 8 + <code>arm-ld</code> (among a bunch of other utils that I don't care 9 + about for now).</p> 10 + <p>Assemble <code>.s</code> files with:</p> 11 + <pre><code class="language-shell">arm-none-eabi-as main.s -g -march=armv8.1-a -o main.out 12 + </code></pre> 13 + <p>The <code>-g</code> flag generates extra debugging information that 14 + <code>gdb</code> picks up. The <code>-march</code> option establishes target 15 + architecture.</p> 16 + <p>Link <code>.o</code> files with:</p> 17 + <pre><code class="language-shell">arm-none-eabi-ld main.out -o main 18 + </code></pre> 19 + <h3 id="running-and-debugging">Running (and Debugging)</h3> 20 + <p>Things get interesting here. <code>gdb</code> on your x86 machine 21 + cannot read nor execute binaries compiled for ARM. So, we 22 + simulate an ARM processor using <code>qemu</code>. Now qemu allows you 23 + to run <code>gdbserver</code> on startup. Connecting our local <code>gdb</code> 24 + instance to <code>gdbserver</code> gives us a view into the program's 25 + execution. Easy!</p> 26 + <p>Run <code>qemu</code>, with <code>gdbserver</code> on port <code>1234</code>, with our ARM 27 + binary, <code>main</code>:</p> 28 + <pre><code class="language-shell">qemu-arm -singlestep -g 1234 main 29 + </code></pre> 30 + <p>Start up <code>gdb</code> on your machine, and connect to <code>qemu</code>'s 31 + <code>gdbserver</code>:</p> 32 + <pre><code>(gdb) set architecture armv8-a 33 + (gdb) target remote localhost:1234 34 + (gdb) file main 35 + Reading symbols from main... # yay! 36 + </code></pre> 37 + <h3 id="gdb-enhanced">GDB Enhanced</h3> 38 + <p><code>gdb</code> is cool, but it's not nearly as comfortable as well 39 + fleshed out emulators/IDEs like Keil. Watching registers, 40 + CPSR and memory chunks update <em>is</em> pretty fun.</p> 41 + <p>I came across <code>gdb</code>'s TUI mode (hit <code>C-x C-a</code> or type <code>tui enable</code> at the prompt). TUI mode is a godsend. It highlights 42 + the current line of execution, shows you disassembly 43 + outputs, updated registers, active breakpoints and more.</p> 44 + <p><em>But</em>, it is an absolute eyesore.</p> 45 + <p>Say hello to <a href="https://github.com/hugsy/gef">GEF</a>! &quot;GDB 46 + Enhanced Features&quot; teaches our old dog some cool new tricks. 47 + Here are some additions that made my ARM debugging 48 + experience loads better:</p> 49 + <ul> 50 + <li>Memory watches 51 + </li> 52 + <li>Register watches, with up to 7 levels of deref (overkill, 53 + I agree) 54 + </li> 55 + <li>Stack tracing 56 + </li> 57 + </ul> 58 + <p>And it's pretty! See for yourself:</p> 59 + <p><a href="https://cdn.oppi.li/wq.png"><img src="https://cdn.oppi.li/wq.png" alt="" /></a></p> 60 + <h3 id="editing">Editing</h3> 61 + <p>Vim, with <code>syntax off</code> because it 62 + dosen't handle GNU ARM syntax too well.</p> 63 + </div></main></div></body></html>
+31
_site/posts/color_conundrum/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>color conundrum</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">color conundrum</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">color conundrum</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>22 sentences</span><span>·</span><span>18.29 cm</span><span>·</span><span>30.12.2019</span></div><p>This piece aims to highlight (pun intended) some of the 3 + reasons behind my <a href="https://cdn.oppi.li/bF.png">color 4 + free</a> editor setup.</p> 5 + <p>Imagine highlighting an entire book because <em>all</em> of it is 6 + important. That is exactly what (most) syntax highlighting 7 + does. It is difficult for the human eye to filter out noise 8 + in rainbow barf. Use color to draw attention, not diverge 9 + it.</p> 10 + <p>At the same time, a book devoid of color is <em>boring!</em> What 11 + is the takeaway from this 10 line paragraph? What are the 12 + technical terms used?</p> 13 + <p>Prose and code are certainly different, but the fickle 14 + minded human eye is the same. The eye constantly looks for a 15 + frame of reference, a focal point. It grows tired when it 16 + can't find one.</p> 17 + <p>The following comparison does a better job of explaining 18 + (none, ample and over-the-top highlighting, from left to 19 + right):</p> 20 + <p><a href="https://cdn.oppi.li/lt.png"><img src="https://cdn.oppi.li/lt.png" alt="" /></a></p> 21 + <p>Without highlighting (far left), it is hard to differentiate 22 + between comments and code! The florid color scheme (far 23 + right) is no good either, it contains too many attention 24 + grabbers. The center sample is a healthy balance of both. 25 + Function calls and constants stand out, and repetitive 26 + keywords and other noise (<code>let</code>, <code>as</code>) are mildly dimmed 27 + out. Comments and non-code text (sign column, status text) 28 + are dimmed further.</p> 29 + <p>I'll stop myself before I rant about color contrast and 30 + combinations.</p> 31 + </div></main></div></body></html>
+307
_site/posts/configuring_jujutsu/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>configuring jujutsu</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">configuring jujutsu</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">configuring jujutsu</h1><div class="text-center space-x-2"><span>7 min</span><span>·</span><span>79 sentences</span><span>·</span><span>188.48 cm</span><span>·</span><span>24.05.2025</span></div><p>There are a lot of reasons to use 3 + <a href="https://github.com/jj-vcs/jj">jujutsu</a>, but this post is 4 + not about that. I have this terrible habit of turning every 5 + knob on a tool before I even fully absorb how it works; and 6 + boy does <code>jj</code> have a lot of knobs.</p> 7 + <h3 id="templates">Templates</h3> 8 + <p><code>jj</code> let you tweak nearly every single character of its 9 + output. In fact, <code>jj</code> includes a full-blown templating 10 + language to do so. Lets start from the basics:</p> 11 + <p>Format all timestamps in relative fashion, like &quot;5 hours 12 + ago&quot;:</p> 13 + <pre><code class="language-toml">[template-aliases] 14 + &quot;format_timestamp(timestamp)&quot; = &quot;timestamp.ago()&quot;; 15 + </code></pre> 16 + <p>The default nodes in the log graph are a bit large for my 17 + taste, we can modify the <code>log_node</code> template to fix that:</p> 18 + <pre><code class="language-toml">[templates] 19 + log_node = ''' 20 + label(&quot;node&quot;, 21 + coalesce( 22 + if(!self, label(&quot;elided&quot;, &quot;~&quot;)), 23 + if(current_working_copy, label(&quot;working_copy&quot;, &quot;@&quot;)), 24 + if(conflict, label(&quot;conflict&quot;, &quot;×&quot;)), 25 + if(immutable, label(&quot;immutable&quot;, &quot;*&quot;)), 26 + label(&quot;normal&quot;, &quot;·&quot;) 27 + ) 28 + ) 29 + ''' 30 + </code></pre> 31 + <p>This uses smaller iconography in <code>jj log</code> (these icons are 32 + also more widely available in fonts, in my experience):</p> 33 + <pre><code>@ wuuownsw me@oppi.li 21 minutes ago 30d1bd12 34 + │ (no description set) 35 + · plmznxvy me@oppi.li 5 hours ago push-plmznxvyqrqw git_head() 051c142e 36 + │ appview: pulls: bump sourceRev for stacks without causing resubmits 37 + │ * towvwqxk x@icyphox.sh 4 hours ago master a588f625 38 + │ │ appview: pages/markup: don't double camo in post process 39 + │ * ryzqnyvs me@oppi.li 4 hours ago ea4b520a 40 + │ │ appview: fix stack merging 41 + │ * kqvutzxr me@oppi.li 4 hours ago ff73ca23 42 + ├─╯ appview: rework RepoLanguages 43 + * vwpqwmms jeynesbrook@gmail.com 8 hours ago d759587b 44 + │ appview: repo: inject language percentage into repo index 45 + ~ 46 + </code></pre> 47 + <p>Much nicer! And speaking of <code>log</code>, I find it hard to parse 48 + the output when every change occupies two lines instead of 49 + just one line, we can remedy that quickly; in fact, <code>jj</code> has 50 + a builtin template for single-line log outputs:</p> 51 + <pre><code>λ jj log -T builtin_log_oneline 52 + @ wuuownsw me@oppi.li 22 minutes ago 30d1bd12 (no description set) 53 + · plmznxvy me@oppi.li 5 hours ago push-plmznxvyqrqw git_head() 051c142e appview: pulls: bump sourceRev for stacks without causing resubmits 54 + │ * towvwqxk x@icyphox.sh 4 hours ago master a588f625 appview: pages/markup: don't double camo in post process 55 + │ * ryzqnyvs me@oppi.li 4 hours ago ea4b520a appview: fix stack merging 56 + │ * kqvutzxr me@oppi.li 4 hours ago ff73ca23 appview: rework RepoLanguages 57 + ├─╯ 58 + * vwpqwmms jeynesbrook 8 hours ago d759587b appview: repo: inject language percentage into repo index 59 + 60 + ~ 61 + </code></pre> 62 + <p>Bit too long! I personally do not always need author and 63 + time information when quickly scrolling through the log, so 64 + I created my own template based on <code>builtin_log_oneline</code>:</p> 65 + <pre><code class="language-toml">[templates] 66 + log = ''' 67 + if(root, 68 + format_root_commit(self), 69 + label(if(current_working_copy, &quot;working_copy&quot;), 70 + concat( 71 + separate(&quot; &quot;, 72 + format_short_change_id_with_hidden_and_divergent_info(self), 73 + if(empty, label(&quot;empty&quot;, &quot;(empty)&quot;)), 74 + if(description, 75 + description.first_line(), 76 + label(if(empty, &quot;empty&quot;), description_placeholder), 77 + ), 78 + bookmarks, 79 + tags, 80 + working_copies, 81 + if(git_head, label(&quot;git_head&quot;, &quot;HEAD&quot;)), 82 + if(conflict, label(&quot;conflict&quot;, &quot;conflict&quot;)), 83 + if(config(&quot;ui.show-cryptographic-signatures&quot;).as_boolean(), 84 + format_short_cryptographic_signature(signature)), 85 + ) ++ &quot;\n&quot;, 86 + ), 87 + ) 88 + ) 89 + ''' 90 + </code></pre> 91 + <p>Which produces:</p> 92 + <pre><code>@ wuuownsw (no description set) 93 + · plmznxvy appview: pulls: bump sourceRev for stacks without causing resubmits push-plmznxvyqrqw HEAD 94 + │ * towvwqxk appview: pages/markup: don't double camo in post process master 95 + │ * ryzqnyvs appview: fix stack merging 96 + │ * kqvutzxr appview: rework RepoLanguages 97 + ├─╯ 98 + * vwpqwmms appview: repo: inject language percentage into repo index 99 + 100 + ~ 101 + </code></pre> 102 + <p>Sweet! To get a more detailed log, you can always use a 103 + different template for the output: <code>jj log -T builtin_log_detailed</code>.</p> 104 + <p>With git, I set <code>commit.verbose</code> to true, this lets me view 105 + the diff when composing a commit messaege in my <code>$EDITOR</code>:</p> 106 + <pre><code class="language-bash">λ git commit 107 + # no message passed, opens $EDITOR to compose message 108 + 109 + # -- inside vim -- 110 + 111 + # Please enter the commit message for your changes. Lines starting 112 + # with '#' will be ignored, and an empty message aborts the commit. 113 + # 114 + # On branch trunk 115 + # Changes to be committed: 116 + # new file: scripts/handle.js 117 + # 118 + # ------------------------ &gt;8 ------------------------ 119 + # Do not modify or remove the line above. 120 + # Everything below it will be ignored. 121 + diff --git a/scripts/handle.js b/scripts/handle.js 122 + new file mode 100644 123 + index 0000000..d8a07f3 124 + --- /dev/null 125 + +++ b/scripts/handle.js 126 + @@ -0,0 +1,104 @@ 127 + +// Run using node handle.js 128 + +// Install axios using npm install axios 129 + +// nodejs v18+ 130 + +const axios = require(&quot;axios&quot;); 131 + . 132 + . 133 + . 134 + </code></pre> 135 + <p>The equivalent in <code>jj</code> requires modifying the 136 + <code>draft_commit_description</code> template:</p> 137 + <pre><code class="language-toml">[templates] 138 + draft_commit_description =''' 139 + concat( 140 + coalesce(description, default_commit_description, &quot;\n&quot;), 141 + surround( 142 + &quot;\nJJ: This commit contains the following changes:\n&quot;, &quot;&quot;, 143 + indent(&quot;JJ: &quot;, diff.stat(72)), 144 + ), 145 + &quot;\nJJ: ignore-rest\n&quot;, 146 + diff.git(), 147 + ) 148 + ''' 149 + </code></pre> 150 + <h3 id="revsets">Revsets</h3> 151 + <p>As a not-so-power-user, I presently only use revsets to 152 + improve my <code>jj log</code> experience. In the context of <code>jj log</code>, 153 + giving it a revset means &quot;here is a bag of revisions, graph 154 + them for me neatly&quot;.</p> 155 + <p>I find myself using the &quot;rebase&quot; flow quite often:</p> 156 + <ul> 157 + <li>I hack on a stack of changes 158 + </li> 159 + <li><code>trunk</code> is updated by my fellow collaborators 160 + </li> 161 + <li>I rebase my stack using <code>jj rebase -s &lt;mine&gt; -d &lt;trunk&gt;</code> 162 + </li> 163 + </ul> 164 + <p>And I scrub through the log output to roughly figure out how 165 + much work it would be to rebase, what I need for this is:</p> 166 + <ul> 167 + <li>the changes added to <code>trunk</code> since I last diverged from it 168 + </li> 169 + <li>the changes add to my stack since I last diverged from 170 + <code>trunk</code> 171 + </li> 172 + </ul> 173 + <pre><code> X--Y--Z my stack 174 + / 175 + F--A--B--C--D trunk 176 + </code></pre> 177 + <p>If you want <code>jj log</code> to print this (and by &quot;this&quot;, I mean, 178 + the graph above, exactly as presented), you have to supply 179 + it a <code>revset</code> argument that grabs X, Y, Z, A, B, C, D and F. 180 + Some examples of revsets are:</p> 181 + <pre><code>@ # the rev marking the working copy 182 + x # the rev identified by shorthand `x` 183 + x | y # the set of two revs, [x, y] 184 + @ | trunk() # the set of two revs, [@, trunk()] 185 + .. # everything in this repository 186 + all() # also everything in this repository 187 + fork_point(@ | trunk()) # the rev where @ and trunk() forked off 188 + </code></pre> 189 + <p>And the revset that captures &quot;ahead-behind&quot; style output is:</p> 190 + <pre><code>trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @) 191 + </code></pre> 192 + <p>Which includes:</p> 193 + <ul> 194 + <li><code>trunk()..@</code>: the new changes that my collaborators have added 195 + </li> 196 + <li><code>@..trunk()</code>: the new changes that I have added 197 + </li> 198 + <li><code>trunk()</code>: the trunk rev itself 199 + </li> 200 + <li><code>@::</code>: all descendants of <code>@</code> 201 + </li> 202 + <li><code>fork_point(trunk() | @)</code>: the rev from which the two 203 + streams of work diverged 204 + </li> 205 + </ul> 206 + <p>Rougly, when working on a stack, this is what I can see by 207 + supplying the above revset expression (simplified output):</p> 208 + <pre><code class="language-bash">λ jj log -r 'trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)' 209 + @ xdihgmke &lt;-- me 210 + · vurstull 211 + · plmznxvy 212 + · ihefghyy 213 + │ * towvwqxk trunk &lt;-- trunk 214 + │ * ryzqnyvs 215 + │ * kqvutzxr 216 + ├─╯ 217 + * vwpqwmms &lt;-- fork-point 218 + </code></pre> 219 + <p>And sometimes, when I am editing older parts of my stack 220 + (<code>@::</code> helps us grab <code>xdihgmke</code> and <code>vurstull</code>):</p> 221 + <pre><code class="language-bash">λ jj log -r 'trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)' 222 + · xdihgmke 223 + · vurstull 224 + @ plmznxvy &lt;-- me 225 + · ihefghyy 226 + │ * towvwqxk trunk &lt;-- trunk 227 + │ * ryzqnyvs 228 + │ * kqvutzxr 229 + ├─╯ 230 + * vwpqwmms &lt;-- fork-point 231 + </code></pre> 232 + <p>To rebase, I would run:</p> 233 + <pre><code>jj rebase -s ihefghyy -d towvwqxk 234 + </code></pre> 235 + <h3 id="aliases">Aliases</h3> 236 + <p>I imagine that most git power users are already familiar 237 + with aliases. The only alias I see myself using often is:</p> 238 + <pre><code class="language-toml">[aliases] 239 + tug = [&quot;bookmark&quot;, &quot;move&quot;, &quot;--from&quot;, &quot;heads(::@- &amp; bookmarks())&quot;, &quot;--to&quot;, &quot;@-&quot;]; 240 + </code></pre> 241 + <p>In action:</p> 242 + <pre><code class="language-bash"># ugh my bookmark is way behind 243 + λ jj log 244 + @ xdihgmke 245 + · vurstull 246 + · cyzmakil 247 + · plmznxvy 248 + · ihefghyy some-bookmark 249 + 250 + ~ 251 + 252 + λ jj tug 253 + 254 + # ready to push! 255 + λ jj log 256 + @ xdihgmke 257 + · vurstull some-bookmark* 258 + · cyzmakil 259 + · plmznxvy 260 + · ihefghyy 261 + 262 + ~ 263 + </code></pre> 264 + <p>This alias was stolen from this <a href="https://gist.github.com/thoughtpolice/8f2fd36ae17cd11b8e7bd93a70e31ad6#file-jjconfig-toml">wonderful 265 + gist</a> 266 + by <a href="https://github.com/jj-vcs/jj/blob/main/docs/testimonials.md?plain=1#L120">Austin 267 + Seipp</a>.</p> 268 + <h3 id="experimental-features">Experimental features</h3> 269 + <p>I use one experimental feature, that is only available on 270 + some of the newer versions of jujutsu:</p> 271 + <pre><code class="language-bash"># built from master 272 + λ jj version 273 + jj 0.29.0-8c7ca30074767257d75e3842581b61e764d022cf 274 + </code></pre> 275 + <pre><code class="language-toml">[git] 276 + write-change-id-header = true 277 + </code></pre> 278 + <p>This writes an extra commit-header (not to be confused with 279 + commit-trailer, which goes directly in the commit body) with 280 + the jujutsu change-id, as seen by inspecting the commit 281 + object:</p> 282 + <pre><code class="language-bash">λ git cat-file commit 4edebe96159bf81c3be662d0a2b4c7b08a062968 283 + tree a9b7e9e3eed8a22c35829bf586d7560ec8396124 284 + parent edc0d2750d4848bc05cfd3255ee1ca916bea9156 285 + author oppiliappan &lt;me@oppi.li&gt; 1747919102 +0100 286 + committer oppiliappan &lt;me@oppi.li&gt; 1747919102 +0100 287 + change-id skrrxvvxlpzrqzpxlxksvryrykpxkvon &lt;-- this 288 + </code></pre> 289 + <p>This allows code forges to extract change-ids from commits, 290 + and most crucially: <strong>allows tracking changes across force 291 + pushes</strong>. This enables the <a href="https://gist.github.com/thoughtpolice/9c45287550a56b2047c6311fbadebed2">interdiff 292 + format</a> 293 + of code-review. Stacking, commit-wise-review, and interdiff 294 + are all supported on <a href="https://tangled.sh">tangled</a> (you can 295 + read more 296 + <a href="https://bsky.app/profile/tangled.sh/post/3lptwcb47kc2u">here</a>), 297 + if you submit a branch with this experimental feature 298 + enabled.</p> 299 + <h3 id="misc">Misc</h3> 300 + <p>Couple of other quality of life additions:</p> 301 + <pre><code class="language-toml">[ui] 302 + default-command = &quot;status&quot; 303 + pager = &quot;delta&quot; 304 + </code></pre> 305 + <p>I also use nix and home-manager to configure jj, you can 306 + find my configuration <a href="https://plonk.li/r/YQ">here</a>.</p> 307 + </div></main></div></body></html>
+273
_site/posts/curing_a_case_of_git-UX/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>curing a case of git-UX</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">curing a case of git-UX</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">curing a case of git-UX</h1><div class="text-center space-x-2"><span>7 min</span><span>·</span><span>143 sentences</span><span>·</span><span>167.65 cm</span><span>·</span><span>03.09.2022</span></div><p>Git worktrees are great, but they fall behind the venerable 3 + <code>git checkout</code> sometimes. I attempted to fix that with 4 + <a href="https://github.com/junegunn/fzf">fzf</a> and 5 + a bit of bash.</p> 6 + <p><a href="https://asciinema.org/a/D297ztKRzpE4gAHbPTPmkqYps"><img src="https://asciinema.org/a/D297ztKRzpE4gAHbPTPmkqYps.svg" alt="" /></a></p> 7 + <p>Fear not if you haven't heard of &quot;worktrees&quot;, I have 8 + included a primer here.<br /> 9 + <a href="#what-makes-them-clunky">Skip the primer 10 + -&gt;</a>.</p> 11 + <h3 id="why-worktrees">Why Worktrees?</h3> 12 + <p>Picture this. You are whacking away on a feature branch. 13 + Halfway there, in fact. Your friend asks you fix something 14 + urgently. You proceed to do one of three things:</p> 15 + <ul> 16 + <li>create a temporary branch, make a WIP commit, begin 17 + working on the fix 18 + </li> 19 + <li>stash away your changes, begin working on the fix 20 + </li> 21 + <li>unfriend said friend for disturbing your flow 22 + </li> 23 + </ul> 24 + <p>All of these options are ... subpar. With the temporary 25 + branch, you are forced to create a partial, non-working 26 + commit, and then reset said commit once done with the fix. 27 + With the stash approach, you are required to now keep a 28 + mental model of the stash, be aware of untracked files that 29 + don't get stashed by default, etc. Why won't git just let 30 + you work on two things at the same time without <em>thinking</em> 31 + so much?</p> 32 + <p>That is exactly what worktrees let you do. Worktrees let you 33 + have more than one checkout at a time, each checkout in a 34 + separate directory. Like creating a new clone, but safer (it 35 + disallows checking out the same branch twice) and a lot more 36 + space efficient (the new working tree is &quot;linked&quot; to the 37 + &quot;main&quot; worktree, and a good amount of stuff is shared). When 38 + your friend asks you to make the fix, you proceed like so:</p> 39 + <ol> 40 + <li>Create a new working tree with: 41 + </li> 42 + </ol> 43 + <pre><code class="language-bash"># git worktree add -b &lt;branch-name&gt; &lt;path&gt; &lt;from&gt; 44 + git worktree add -b fix-stuff /path/to/tree master 45 + </code></pre> 46 + <ol start="2"> 47 + <li><code>cd</code> into <code>/path/to/tree</code> 48 + </li> 49 + <li>Fix, test, commit, push, party 50 + </li> 51 + <li>Go back to your work, <code>cd -</code> 52 + </li> 53 + </ol> 54 + <p>Easy as cake. You didn't have to settle for a partially 55 + working commit, you didn't to deal with this &quot;stash&quot; thing, 56 + <em>and</em> you didn't have to unfriend your friend. Treating each 57 + branch as a directory just <em>feels</em> more intuitive, more 58 + UNIX-y.</p> 59 + <p>A few weeks later, you find yourself singing in praise of 60 + worktrees, working on several things simultaneously. And at 61 + the same time, cursing them for being a little ... clunky.</p> 62 + <h3 id="what-makes-them-clunky">What makes them clunky?</h3> 63 + <p>Worktrees are great at what they claim to do. They stay out 64 + of the way when you need a checkout posthaste. However, as 65 + you start using them regularly, you realize they are not as 66 + flexible as <code>git checkout</code> or <code>git switch</code>.</p> 67 + <h4 id="branch-hopping">Branch-hopping</h4> 68 + <p>You can <code>git checkout &lt;branch&gt;</code> from anywhere within a git 69 + repository. You can't &quot;jump&quot; to a worktree in the same 70 + fashion. The closest you can get, is to run <code>git worktree list</code>, copy the path corresponding to your branch, and <code>cd</code> 71 + into it.</p> 72 + <p>Branch-hopping with the good ol' git-checkout:</p> 73 + <pre><code class="language-bash"># anywhere, anytime 74 + λ git checkout feature/is-ascii-octdigit 75 + </code></pre> 76 + <p>Meanwhile, in worktree world:</p> 77 + <pre><code class="language-bash"># keeping these paths in your head is hard 78 + λ git worktree list 79 + ~/worktrees/rustc/master eac6c33bc63 [master] 80 + ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 81 + ~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit] 82 + ~/my/other/path/oh/god op57or3ns7n [fix/some-error] 83 + 84 + λ cd ~/worktrees/rustc/is-ascii-octdigit 85 + </code></pre> 86 + <h4 id="branch-previewing">Branch-previewing</h4> 87 + <p>You can &quot;preview&quot; branches with <code>git branch -v</code>. However, to 88 + get an idea of what &quot;recent activity&quot; on a worktree looks 89 + like, you might need some juggling. You can't glean much 90 + info about a worktree in a jiffy.</p> 91 + <p>Branch-previewing with the good ol' git-branch:</p> 92 + <pre><code class="language-bash">λ git branch -v 93 + + feature/is-ascii-octdigit bc57be3af7a introduce {char, u8}::is_ ... 94 + + improve-std-char-docs 94cba88553e add whitespace in assert ... 95 + * master eac6c33bc63 Auto merge of #100869 - n ... 96 + </code></pre> 97 + <p>Meanwhile in worktree wonderland:</p> 98 + <pre><code>λ git worktree list 99 + ~/worktrees/rustc/master eac6c33bc63 [master] 100 + ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 101 + ~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit] 102 + 103 + # aha, so ../is-ascii-octdigit corresponds to `feature/is-ascii-octdigit` 104 + λ git log feature/is-ascii-octdigit 105 + bc57be3af7a introduce {char, u8}::is_ascii_octdigit 106 + eac6c33bc63 Auto merge of #100869 - nnethercote:repl ... 107 + b32223fec10 Auto merge of #100707 - dzvon:fix-typo, ... 108 + aa857eb953e Auto merge of #100537 - petrochenkov:pic ... 109 + 110 + # extra work to make the branch &lt;-&gt; worktree correspondence 111 + </code></pre> 112 + <h4 id="shell-completions">Shell completions</h4> 113 + <p>Lastly, you can bank on shell completions to fill in your 114 + branch whilst using <code>git checkout</code>. Worktrees have no such 115 + conveniences.</p> 116 + <p>We can mend these minor faults with fzf.</p> 117 + <h3 id="unclunkifying-worktrees">Unclunkifying worktrees</h3> 118 + <p>I'd suggest looking up 119 + <a href="https://github.com/junegunn/fzf">fzf</a> (or 120 + <a href="https://github.com/lotabout/skim">skim</a> or 121 + <a href="https://github.com/jhawthorn/fzy">fzy</a>). These things make 122 + it cake-easy to add interactivity to your shell. Onto fixing 123 + the first minor fault, the inability to &quot;jump&quot; to a worktree 124 + from anywhere within a git repository.</p> 125 + <p>I have a little function called <code>gwj</code> which stands for &quot;git 126 + worktree jump&quot;. The idea is to list all the worktrees, 127 + select one with fzf, and <code>cd</code> to it upon selection:</p> 128 + <pre><code class="language-bash">gwj () { 129 + local out 130 + out=$(git worktree list | fzf | awk '{print $1}') 131 + cd $out 132 + } 133 + </code></pre> 134 + <p>That is all of it really. Head into a git repository:</p> 135 + <pre><code class="language-bash"># here, &quot;master&quot; is a directory, which contains my main 136 + # worktree: a checkout of the master branch on rust-lang/rust 137 + λ cd ~/worktrees/rustc/master/library/core/src 138 + λ # hack away 139 + </code></pre> 140 + <p>Preferably one with a few worktrees:</p> 141 + <pre><code class="language-bash">λ git worktree list 142 + ~/worktrees/rustc/master eac6c33bc63 [master] 143 + ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 144 + ~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit] 145 + </code></pre> 146 + <p>And hit <code>gwj</code> (pretend that the pipe, |, is your cursor):</p> 147 + <pre><code class="language-bash">λ gwj 148 + &gt; | 149 + 4/4 150 + &gt; ~/worktrees/rustc/master eac6c33bc63 [master] 151 + ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 152 + ~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit] 153 + </code></pre> 154 + <p>Approximately type in your branch of choice:</p> 155 + <pre><code class="language-bash">λ gwj 156 + &gt; docs| 157 + 4/4 158 + &gt; ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 159 + </code></pre> 160 + <p>And hit enter. You should find yourself in the selected 161 + worktree.</p> 162 + <p>Onward, to the next fault, lack of preview-bility. We can 163 + utilize fzf's aptly named <code>--preview</code> flag, to, well, 164 + preview our worktree before performing a selection:</p> 165 + <pre><code class="language-bash">gwj () { 166 + local out 167 + out=$( 168 + git worktree list | 169 + fzf --preview='git log --oneline -n10 {2}' | 170 + awk '{print $1}' 171 + ) 172 + cd $out 173 + } 174 + </code></pre> 175 + <p>Once again, hit <code>gwj</code> inside a git repository with linked worktrees:</p> 176 + <pre><code class="language-bash">λ gwj 177 + ╭─────────────────────────────────────────────────────────╮ 178 + │ eac6c33bc63 Auto merge of 100869 nnethercote:replace... │ 179 + │ b32223fec10 Auto merge of 100707 dzvon:fix-typo, r=d... │ 180 + │ aa857eb953e Auto merge of 100537 petrochenkov:picche... │ 181 + │ 3892b7074da Auto merge of 100210 mystor:proc_macro_d... │ 182 + │ db00199d999 Auto merge of 101249 matthiaskrgr:rollup... │ 183 + │ 14d216d33ba Rollup merge of 101240 JohnTitor:JohnTit... │ 184 + │ 3da66f03531 Rollup merge of 101236 thomcc:winfs-noze... │ 185 + │ 0620f6e90af Rollup merge of 101230 davidtwco:transla... │ 186 + │ c30c42ee299 Rollup merge of 101229 mgeisler:link-try... │ 187 + │ e5356712b9e Rollup merge of 101165 ldm0:drain_to_ite... │ 188 + ╰─────────────────────────────────────────────────────────╯ 189 + &gt; 190 + 4/4 191 + &gt; /home/np/worktrees/compiler/master eac6c... 192 + /home/np/worktrees/compiler/improve-std-char-docs 94cba... 193 + /home/np/worktrees/compiler/is-ascii-octdigit bc57b... 194 + </code></pre> 195 + <p>A fancy preview of the last 10 commits on the branch that 196 + the selected worktree corresponds to. In other words, sight 197 + for sore eyes. Our little script is already shaping up to be 198 + useful, you hit <code>gwj</code>, browse through your worktrees, 199 + preview each one and automatically <code>cd</code> to your selection. 200 + But we are not done yet.</p> 201 + <p>The last fault was lack shell completions. A quick review of 202 + what a shell completion really does:</p> 203 + <pre><code class="language-bash">λ git checkout f&lt;tab&gt; 204 + feature/is-ascii-octdigit 205 + fix/some-error 206 + format-doc-tests 207 + 208 + λ git checkout feat&lt;tab&gt; 209 + 210 + λ git checkout feature/is-ascii-octdigit 211 + </code></pre> 212 + <p>Each time you hit &quot;tab&quot;, the shell produces a few 213 + &quot;completion candidates&quot;, and once you have just a single 214 + candidate left, the shell inserts that for you directly into 215 + your edit line. Of course, this process varies from shell to 216 + shell.</p> 217 + <p>fzf narrows down your options as you type into the prompt, 218 + but you still have to:</p> 219 + <ol> 220 + <li>Type <code>gwj</code> 221 + </li> 222 + <li>Hit enter 223 + </li> 224 + <li>Type out a query and narrow down your search 225 + </li> 226 + <li>Hit enter 227 + </li> 228 + </ol> 229 + <p>We can speed that up a bit, have fzf narrow down the 230 + candidates on startup, just like our shell does:</p> 231 + <pre><code class="language-bash">gwj () { 232 + local out query 233 + query=&quot;${1:- }&quot; 234 + out=$( 235 + git worktree list | 236 + fzf --preview='git log --oneline -n10 {2}' --query &quot;$query&quot; -1 | 237 + awk '{print $1}' 238 + ) 239 + cd $out 240 + } 241 + </code></pre> 242 + <p>The change is extremely tiny, blink-and-you'll-miss-it kinda 243 + tiny. We added a little <code>--query</code> flag, that allows you to 244 + prefill the prompt, and the <code>-1</code> flag, that avoids the 245 + interactive finder if only one match exists on startup:</p> 246 + <pre><code class="language-bash"># skip through the fzf prompt: 247 + λ gwj master 248 + # cd -- ~/worktrees/rustc/master 249 + 250 + # more than one option, we end up in the interactive finder 251 + λ gwj improve 252 + ╭─────────────────────────────────────────────────────────╮ 253 + │ eac6c33bc63 Auto merge of 100869 nnethercote:replace... │ 254 + │ b32223fec10 Auto merge of 100707 dzvon:fix-typo, r=d... │ 255 + │ aa857eb953e Auto merge of 100537 petrochenkov:picche... │ 256 + ╰─────────────────────────────────────────────────────────╯ 257 + &gt; improve 258 + 2/2 259 + &gt; /home/np/worktrees/compiler/improve-const-perf eac6c... 260 + /home/np/worktrees/compiler/improve-std-char-docs 94cba... 261 + </code></pre> 262 + <p>Throw some error handling in there, hook up a similar script 263 + to improve the UX of <code>git worktree remove</code>, go wild. A few 264 + more helpers I've got:</p> 265 + <pre><code class="language-bash"># gwa /path/to/branch-name 266 + # creates a new branch and &quot;switches&quot; to it 267 + function gwa () { 268 + git worktree add &quot;$1&quot; &amp;&amp; cd &quot;$1&quot; 269 + } 270 + 271 + alias gwls=&quot;git worktree list&quot; 272 + </code></pre> 273 + </div></main></div></body></html>
+58
_site/posts/font_size_fallacies/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>font size fallacies</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">font size fallacies</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">font size fallacies</h1><div class="text-center space-x-2"><span>2 min</span><span>·</span><span>44 sentences</span><span>·</span><span>41.15 cm</span><span>·</span><span>16.03.2020</span></div><p>I am not an expert with fonts, but I do have some 3 + experience <a href="https://github.com/nerdypepper/scientifica">^exp</a>, and common sense. This post aims to debunk some 4 + misconceptions about font sizes!</p> 5 + <p>11 px on your display is <em>probably not</em> 11 px on my display. 6 + Let's do some quick math. I have two displays, 1366x768 @ 7 + 21&quot; and another with 1920x1080 @ 13&quot;, call them <code>A</code> and 8 + <code>B</code> for now.</p> 9 + <p>Display <code>A</code> has 1,049,088 pixels. A pixel is a square, of 10 + side say, <code>s</code> cm. The total area covered by my 21&quot; display 11 + is about 1,066 cm^2 (41x26). Thus,</p> 12 + <pre><code>Display A 13 + Dimensions: 1366x768 @ 21&quot; (41x26 sq. cm) 14 + 1,049,088 s^2 = 1066 15 + s = 0.0318 cm (side of a pixel on Display A) 16 + </code></pre> 17 + <p>Bear with me, as I repeat the number crunching for Display 18 + <code>B</code>:</p> 19 + <pre><code>Display B 20 + Dimensions: 1920x1080 @ 13&quot; (29.5x16.5 sq. cm) 21 + 2,073,600 s^2 = 486.75 22 + s = 0.0153 cm (side of a pixel on Display B) 23 + </code></pre> 24 + <p>The width of a pixel on Display <code>A</code> is <em>double</em> the width of a 25 + pixel on Display <code>B</code>. The area occupied by a pixel on Display 26 + <code>A</code> is <em>4 times</em> the area occupied by a pixel on Display <code>B</code>.</p> 27 + <p><em>The size of a pixel varies from display to display!</em></p> 28 + <p>A 5x11 bitmap font on Display <code>A</code> would be around 4 mm tall 29 + whereas the same bitmap font on Display <code>B</code> would be around 30 + 1.9 mm tall. A 11 px tall character on <code>B</code> is visually 31 + equivalent to a 5 px character on <code>A</code>. When you view a 32 + screenshot of Display <code>A</code> on Display <code>B</code>, the contents are 33 + shrunk down by a factor of 2!</p> 34 + <p>So screen resolution is not enough, how else do we measure 35 + size? Pixel Density! Keen readers will realize that the 5^th 36 + grade math problem we solved up there showcases pixel 37 + density, or, pixels per cm (PPCM). Usually we deal with 38 + pixels per inch (PPI).</p> 39 + <p><strong>Note:</strong> PPI is not to be confused with DPI <a href="https://en.wikipedia.org/wiki/Dots_per_inch">^dpi</a> (dots 40 + per inch). DPI is defined for printers.</p> 41 + <p>In our example, <code>A</code> is a 75 ppi display and <code>B</code> is around 42 + 165 ppi <a href="https://www.sven.de/dpi/">^ppi</a>. A low ppi display appears to be 43 + 'pixelated', because the pixels are more prominent, much 44 + like Display <code>A</code>. A higher ppi usually means you can view 45 + larger images and render crispier fonts. The average desktop 46 + display can stuff 100-200 pixels per inch. Smart phones 47 + usually fall into the 400-600 ppi (XXXHDPI) category. The 48 + human eye fails to differentiate detail past 300 ppi.</p> 49 + <p><em>So ... streaming an 8K video on a 60&quot; TV provides the same 50 + clarity as a HD video on a smart phone?</em></p> 51 + <p>Absolutely. Well, clarity is subjective, but the amount of 52 + detail you can discern on mobile displays has always been 53 + limited. Salty consumers of the Xperia 1 <a href="https://en.wikipedia.org/wiki/Sony_Xperia_1">^sony</a> will say 54 + otherwise.</p> 55 + <p>Maybe I will talk about font rendering in another post, but 56 + thats all for now. Don't judge a font size by its 57 + screenshot.</p> 58 + </div></main></div></body></html>
+28
_site/posts/get_better_at_yanking_and_putting_in_vim/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>get better at yanking and putting in vim</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">get better at yanking and putting in vim</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">get better at yanking and putting in vim</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>3 sentences</span><span>·</span><span>14.22 cm</span><span>·</span><span>30.07.2019</span></div><p>a couple of nifty tricks to help you copy-paste better:</p> 3 + <ol> 4 + <li> 5 + <p>reselecting previously selected text (i use this to fix botched selections):</p> 6 + <pre><code>gv &quot; :h gv for more 7 + &quot; you can use `o` in visual mode to go to the `Other` end of the selection 8 + &quot; use a motion to fix the selection 9 + </code></pre> 10 + </li> 11 + <li> 12 + <p>reselecting previously yanked text:</p> 13 + <pre><code>`[v`] 14 + `[ &quot; marks the beginning of the previously yanked text :h `[ 15 + `] &quot; marks the end :h `] 16 + v &quot; visual select everything in between 17 + 18 + nnoremap gb `[v`] &quot; &quot;a quick map to perform the above 19 + </code></pre> 20 + </li> 21 + <li> 22 + <p>pasting and indenting text (in one go):</p> 23 + <pre><code>]p &quot; put (p) and adjust indent to current line 24 + ]P &quot; put the text before the cursor (P) and adjust indent to current line 25 + </code></pre> 26 + </li> 27 + </ol> 28 + </div></main></div></body></html>
+157
_site/posts/gripes_with_go/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>gripes with go</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">gripes with go</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">gripes with go</h1><div class="text-center space-x-2"><span>3 min</span><span>·</span><span>40 sentences</span><span>·</span><span>97.54 cm</span><span>·</span><span>01.08.2020</span></div><p>You've read a lot of posts about the shortcomings of the Go 3 + programming language, so what's one more.</p> 4 + <ol> 5 + <li><a href="#lack-of-sum-types">Lack of sum types</a> 6 + </li> 7 + <li><a href="#type-assertions">Type assertions</a> 8 + </li> 9 + <li><a href="#date-and-time">Date and Time</a> 10 + </li> 11 + <li><a href="#statements-over-expressions">Statements over Expressions</a> 12 + </li> 13 + <li><a href="#erroring-out-on-unused-variables">Erroring out on unused variables</a> 14 + </li> 15 + <li><a href="#error-handling">Error handling</a> 16 + </li> 17 + </ol> 18 + <h3 id="lack-of-sum-types">Lack of Sum types</h3> 19 + <p>A &quot;Sum&quot; type is a data type that can hold one of many states 20 + at a given time, similar to how a boolean can hold a true or 21 + a false, not too different from an <code>enum</code> type in C. Go 22 + lacks <code>enum</code> types unfortunately, and you are forced to 23 + resort to crafting your own substitute.</p> 24 + <p>A type to represent gender for example:</p> 25 + <pre><code class="language-go">type Gender int 26 + 27 + const ( 28 + Male Gender = iota // assigns Male to 0 29 + Female // assigns Female to 1 30 + Other // assigns Other to 2 31 + ) 32 + 33 + fmt.Println(&quot;My gender is &quot;, Male) 34 + // My gender is 0 35 + // Oops! We have to implement String() for Gender ... 36 + 37 + func (g Gender) String() string { 38 + switch (g) { 39 + case 0: return &quot;Male&quot; 40 + case 1: return &quot;Female&quot; 41 + default: return &quot;Other&quot; 42 + } 43 + } 44 + 45 + // You can accidentally do stupid stuff like: 46 + gender := Male + 1 47 + </code></pre> 48 + <p>The Haskell equivalent of the same:</p> 49 + <pre><code class="language-haskell">data Gender = Male 50 + | Female 51 + | Other 52 + deriving (Show) 53 + 54 + print $ &quot;My gender is &quot; ++ (show Male) 55 + </code></pre> 56 + <h3 id="type-assertions">Type Assertions</h3> 57 + <p>A downcast with an optional error check? What could go 58 + wrong?</p> 59 + <p>Type assertions in Go allow you to do:</p> 60 + <pre><code class="language-go">var x interface{} = 7 61 + y, goodToGo := x.(int) 62 + if goodToGo { 63 + fmt.Println(y) 64 + } 65 + </code></pre> 66 + <p>The error check however is optional:</p> 67 + <pre><code class="language-go">var x interface{} = 7 68 + var y := x.(float64) 69 + fmt.Println(y) 70 + // results in a runtime error: 71 + // panic: interface conversion: interface {} is int, not float64 72 + </code></pre> 73 + <h3 id="date-and-time">Date and Time</h3> 74 + <p>Anyone that has written Go previously, will probably already 75 + know what I am getting at here. For the uninitiated, parsing 76 + and formatting dates in Go requires a &quot;layout&quot;. This 77 + &quot;layout&quot; is based on magical reference date:</p> 78 + <pre><code>Mon Jan 2 15:04:05 MST 2006 79 + </code></pre> 80 + <p>Which is the date produced when you write the first seven 81 + natural numbers like so:</p> 82 + <pre><code>01/02 03:04:05 '06 -0700 83 + </code></pre> 84 + <p>Parsing a string in <code>YYYY-MM-DD</code> format would look something 85 + like:</p> 86 + <pre><code class="language-go">const layout = &quot;2006-01-02&quot; 87 + time.Parse(layout, &quot;2020-08-01&quot;) 88 + </code></pre> 89 + <p>This so-called &quot;intuitive&quot; method of formatting dates 90 + doesn't allow you to print <code>0000 hrs</code> as <code>2400 hrs</code>, it 91 + doesn't allow you to omit the leading zero in 24 hour 92 + formats. It is rife with inconveniences, if only there were 93 + a <a href="https://man7.org/linux/man-pages/man3/strftime.3.html">tried and 94 + tested</a> 95 + date formatting convention ...</p> 96 + <h3 id="statements-over-expressions">Statements over Expressions</h3> 97 + <p>Statements have side effects, expressions return values. 98 + More often than not, expressions are easier to understand at 99 + a glance: evaluate the LHS and assign the same to the RHS.</p> 100 + <p>Rust allows you to create local namespaces, and treats 101 + blocks (<code>{}</code>) as expressions:</p> 102 + <pre><code class="language-rust">let twenty_seven = { 103 + let three = 1 + 2; 104 + let nine = three * three; 105 + nine * three 106 + }; 107 + </code></pre> 108 + <p>The Go equivalent of the same:</p> 109 + <pre><code class="language-go">twenty_seven := nil 110 + 111 + three := 1 + 2 112 + nine := three * three 113 + twenty_seven = nine * three 114 + </code></pre> 115 + <h3 id="erroring-out-on-unused-variables">Erroring out on unused variables</h3> 116 + <p>Want to quickly prototype something? Go says no! In all 117 + seriousness, a warning would suffice, I don't want to have 118 + to go back and comment each unused import out, only to come 119 + back and uncomment them a few seconds later.</p> 120 + <h3 id="error-handling">Error handling</h3> 121 + <pre><code class="language-go">if err != nil { ... } 122 + </code></pre> 123 + <p>Need I say more? I will, for good measure:</p> 124 + <ol> 125 + <li>Error handling is optional 126 + </li> 127 + <li>Errors are propagated via a clunky <code>if</code> + <code>return</code> statement 128 + </li> 129 + </ol> 130 + <p>I prefer Haskell's &quot;Monadic&quot; error handling, which is 131 + employed by Rust as well:</p> 132 + <pre><code class="language-rust">// 1. error handling is compulsory 133 + // 2. errors are propagated with the `?` operator 134 + fn foo() -&gt; Result&lt;String, io::Error&gt; { 135 + let mut f = File::open(&quot;foo.txt&quot;)?; // return if error 136 + let mut s = String::new(); 137 + 138 + f.read_to_string(&amp;mut s)?; // return if error 139 + 140 + Ok(s) // all good, return a string inside a `Result` context 141 + } 142 + 143 + fn main() { 144 + // `contents` is an enum known as Result: 145 + let contents = foo(); 146 + match contents { 147 + Ok(c) =&gt; println!(c), 148 + Err(e) =&gt; eprintln!(e) 149 + } 150 + } 151 + </code></pre> 152 + <h3 id="conclusion">Conclusion</h3> 153 + <p>I did not want to conclude without talking about stylistic 154 + choices, lack of metaprogramming, bizzare export rules, but, 155 + I am too busy converting my <code>interface{}</code> types into actual 156 + generic code for Go v2.</p> 157 + </div></main></div></body></html>
+18
_site/posts/hold_position!/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>hold position!</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">hold position!</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">hold position!</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>5 sentences</span><span>·</span><span>11.68 cm</span><span>·</span><span>30.07.2019</span></div><p>Often times, when I run a vim command that makes &quot;big&quot; changes to a file (a 3 + macro or a <code>:vimgrep</code> command) I lose my original position and feel disoriented.</p> 4 + <p><em>Save position with <code>winsaveview()</code>!</em></p> 5 + <p>The <code>winsaveview()</code> command returns a <code>Dictionary</code> that contains information 6 + about the view of the current window. This includes the cursor line number, 7 + cursor coloumn, the top most line in the window and a couple of other values, 8 + none of which concern us.</p> 9 + <p>Before running our command (one that jumps around the buffer, a lot), we save 10 + our view, and restore it once its done, with <code>winrestview</code>.</p> 11 + <pre><code>let view = winsaveview() 12 + s/\s\+$//gc &quot; find and (confirm) replace trailing blanks 13 + winrestview(view) &quot; restore our original view! 14 + </code></pre> 15 + <p>It might seem a little overkill in the above example, just use `` (double 16 + backticks) instead, but it comes in handy when you run your file through 17 + heavier filtering.</p> 18 + </div></main></div></body></html>
+2
_site/posts/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>posts</title><link rel="stylesheet" href="/style.css?v=c98bdd9f" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="#">posts</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto"><h1 class="text-center">posts</h1><div class="text-center space-x-2"><span>33 entries</span><span>·</span><span>6 years</span></div><div class="mt-4"><div class="flex"><a href="mounting_the_atmosphere">mounting the atmosphere</a><span class="grow text-right tabular-nums">31.07.2025</span></div><div class="flex"><a href="configuring_jujutsu">configuring jujutsu</a><span class="grow text-right tabular-nums">24.05.2025</span></div><div class="flex"><a href="tales_from_mainframe_modernization">tales from mainframe modernization</a><span class="grow text-right tabular-nums">21.05.2025</span></div><div class="flex"><a href="OSC-52">OSC-52</a><span class="grow text-right tabular-nums">27.11.2024</span></div><div class="flex"><a href="introducing_tablespoon">introducing tablespoon</a><span class="grow text-right tabular-nums">01.08.2024</span></div><div class="flex"><a href="snip_snap">snip snap</a><span class="grow text-right tabular-nums">29.05.2024</span></div><div class="flex"><a href="plain_text_journaling">plain text journaling</a><span class="grow text-right tabular-nums">18.06.2023</span></div><div class="flex"><a href="curing_a_case_of_git-UX">curing a case of git-UX</a><span class="grow text-right tabular-nums">03.09.2022</span></div><div class="flex"><a href="programming_on_34_keys">programming on 34 keys</a><span class="grow text-right tabular-nums">28.08.2022</span></div><div class="flex"><a href="a_reference_counted_afterlife">a reference counted afterlife</a><span class="grow text-right tabular-nums">02.08.2022</span></div><div class="flex"><a href="lotus58">lotus58</a><span class="grow text-right tabular-nums">13.06.2022</span></div><div class="flex"><a href="lightweight_linting">lightweight linting</a><span class="grow text-right tabular-nums">26.01.2022</span></div><div class="flex"><a href="novice_nix:_flake_templates">novice nix: flake templates</a><span class="grow text-right tabular-nums">05.10.2021</span></div><div class="flex"><a href="SDL2_devlog">SDL2 devlog</a><span class="grow text-right tabular-nums">11.04.2021</span></div><div class="flex"><a href="self-hosting_git">self-hosting git</a><span class="grow text-right tabular-nums">17.10.2020</span></div><div class="flex"><a href="nixOS">nixOS</a><span class="grow text-right tabular-nums">01.09.2020</span></div><div class="flex"><a href="gripes_with_go">gripes with go</a><span class="grow text-right tabular-nums">01.08.2020</span></div><div class="flex"><a href="turing_complete_type_systems">turing complete type systems</a><span class="grow text-right tabular-nums">17.06.2020</span></div><div class="flex"><a href="auto-currying_rust_functions">auto-currying rust functions</a><span class="grow text-right tabular-nums">08.05.2020</span></div><div class="flex"><a href="pixel_art_in_GIMP">pixel art in GIMP</a><span class="grow text-right tabular-nums">08.04.2020</span></div><div class="flex"><a href="rapid_refactoring_with_vim">rapid refactoring with vim</a><span class="grow text-right tabular-nums">31.03.2020</span></div><div class="flex"><a href="font_size_fallacies">font size fallacies</a><span class="grow text-right tabular-nums">16.03.2020</span></div><div class="flex"><a href="termux_tandem">termux tandem</a><span class="grow text-right tabular-nums">07.03.2020</span></div><div class="flex"><a href="call_to_ARMs">call to ARMs</a><span class="grow text-right tabular-nums">07.02.2020</span></div><div class="flex"><a href="color_conundrum">color conundrum</a><span class="grow text-right tabular-nums">30.12.2019</span></div><div class="flex"><a href="static_sites_with_bash">static sites with bash</a><span class="grow text-right tabular-nums">22.11.2019</span></div><div class="flex"><a href="my_setup">my setup</a><span class="grow text-right tabular-nums">06.11.2019</span></div><div class="flex"><a href="WPA_woes">WPA woes</a><span class="grow text-right tabular-nums">12.10.2019</span></div><div class="flex"><a href="bye_bye_BDFs">bye bye BDFs</a><span class="grow text-right tabular-nums">07.08.2019</span></div><div class="flex"><a href="onivim_sucks">onivim sucks</a><span class="grow text-right tabular-nums">02.08.2019</span></div><div class="flex"><a href="bash_harder_with_vim">bash harder with vim</a><span class="grow text-right tabular-nums">30.07.2019</span></div><div class="flex"><a href="hold_position!">hold position!</a><span class="grow text-right tabular-nums">30.07.2019</span></div><div class="flex"><a href="get_better_at_yanking_and_putting_in_vim">get better at yanking and putting in vim</a><span class="grow text-right tabular-nums">30.07.2019</span></div></div></div></main></div></body></html>
+154
_site/posts/introducing_tablespoon/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>introducing tablespoon</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">introducing tablespoon</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">introducing tablespoon</h1><div class="text-center space-x-2"><span>3 min</span><span>·</span><span>87 sentences</span><span>·</span><span>91.95 cm</span><span>·</span><span>01.08.2024</span></div><p><a href="https://git.peppe.rs/languages/tbsp">tbsp</a> (tree-based 3 + source-processing language) is an awk-like language that 4 + operates on tree-sitter syntax trees. To motivate the need 5 + for such a program, we could begin by writing a 6 + markdown-to-html converter using <code>tbsp</code> and 7 + <a href="https://github.com/tree-sitter-grammars/tree-sitter-markdown">tree-sitter-md</a>. 8 + We need some markdown to begin with:</p> 9 + <pre><code># 1 heading 10 + 11 + content of first paragraph 12 + 13 + ## 1.1 heading 14 + 15 + content of nested paragraph 16 + </code></pre> 17 + <p>For future reference, this markdown is parsed like so by 18 + tree-sitter-md (visualization generated by 19 + <a href="https://git.peppe.rs/cli/tree-viz">tree-viz</a>):</p> 20 + <pre><code>document 21 + | section 22 + | | atx_heading 23 + | | | atx_h1_marker &quot;#&quot; 24 + | | | heading_content inline &quot;1 heading&quot; 25 + | | paragraph 26 + | | | inline &quot;content of first paragraph&quot; 27 + | | section 28 + | | | atx_heading 29 + | | | | atx_h2_marker &quot;##&quot; 30 + | | | | heading_content inline &quot;1.1 heading&quot; 31 + | | | paragraph 32 + | | | | inline &quot;content of nested paragraph&quot; 33 + </code></pre> 34 + <p>Onto the converter itself. Every <code>tbsp</code> program is written as 35 + a collection of stanzas. Typically, we start with a stanza 36 + like so:</p> 37 + <pre><code>BEGIN { 38 + int depth = 0; 39 + 40 + print(&quot;&lt;html&gt;\n&quot;); 41 + print(&quot;&lt;body&gt;\n&quot;); 42 + } 43 + </code></pre> 44 + <p>The stanza begins with a &quot;pattern&quot;, in this case, <code>BEGIN</code>, 45 + and is followed a block of code. This block specifically, is 46 + executed right at the beginning, before traversing the parse 47 + tree. In this stanza, we set a &quot;depth&quot; variable to keep 48 + track of nesting of markdown headers, and begin our html 49 + document by printing the <code>&lt;html&gt;</code> and <code>&lt;body&gt;</code> tags.</p> 50 + <p>We can follow this stanza with an <code>END</code> stanza, that is 51 + executed after the traversal:</p> 52 + <pre><code>END { 53 + print(&quot;&lt;/body&gt;\n&quot;); 54 + print(&quot;&lt;/html&gt;\n&quot;); 55 + } 56 + </code></pre> 57 + <p>In this stanza, we close off the tags we opened at the start 58 + of the document. We can move onto the interesting bits of 59 + the conversion now:</p> 60 + <pre><code>enter section { 61 + depth += 1; 62 + } 63 + leave section { 64 + depth -= 1; 65 + } 66 + </code></pre> 67 + <p>The above stanzas begin with <code>enter</code> and <code>leave</code> clauses, 68 + followed by the name of a tree-sitter node kind: <code>section</code>. 69 + The <code>section</code> identifier is visible in the 70 + tree-visualization above, it encompasses a markdown-section, 71 + and is created for every markdown header. To understand how 72 + <code>tbsp</code> executes above stanzas:</p> 73 + <pre><code>document ... depth = 0 74 + | section &lt;-------- enter section (1) ... depth = 1 75 + | | atx_heading 76 + | | | inline 77 + | | paragraph 78 + | | | inline 79 + | | section &lt;----- enter section (2) ... depth = 2 80 + | | | atx_heading 81 + | | | | inline 82 + | | | paragraph 83 + | | | | inline 84 + | | | &lt;----------- leave section (2) ... depth = 1 85 + | | &lt;-------------- leave section (1) ... depth = 0 86 + </code></pre> 87 + <p>The following stanzas should be self-explanatory now:</p> 88 + <pre><code>enter atx_heading { 89 + print(&quot;&lt;h&quot;); 90 + print(depth); 91 + print(&quot;&gt;&quot;); 92 + } 93 + leave atx_heading { 94 + print(&quot;&lt;/h&quot;); 95 + print(depth); 96 + print(&quot;&gt;\n&quot;); 97 + } 98 + 99 + enter inline { 100 + print(text(node)); 101 + } 102 + </code></pre> 103 + <p>But an explanation is included nonetheless:</p> 104 + <pre><code>document ... depth = 0 105 + | section &lt;-------- enter section (1) ... depth = 1 106 + | | atx_heading &lt;- enter atx_heading ... print &quot;&lt;h1&gt;&quot; 107 + | | | inline &lt;--- enter inline ... print .. 108 + | | | &lt;----------- leave atx_heading ... print &quot;&lt;/h1&gt;&quot; 109 + | | paragraph 110 + | | | inline &lt;--- enter inline ... print .. 111 + | | section &lt;----- enter section (2) ... depth = 2 112 + | | | atx_heading enter atx_heading ... print &quot;&lt;h2&gt;&quot; 113 + | | | | inline &lt;- enter inline ... print .. 114 + | | | | &lt;-------- leave atx_heading ... print &quot;&lt;/h2&gt;&quot; 115 + | | | paragraph 116 + | | | | inline &lt;- enter inline ... print .. 117 + | | | &lt;----------- leave section (2) ... depth = 1 118 + | | &lt;-------------- leave section (1) ... depth = 0 119 + </code></pre> 120 + <p>The 121 + <a href="https://git.peppe.rs/languages/tbsp/tree/examples">examples</a> 122 + directory contains a complete markdown-to-html converter, 123 + along with a few other motivating examples.</p> 124 + <h3 id="usage">Usage</h3> 125 + <p>The <code>tbsp</code> evaluator is written in rust, use cargo to build 126 + and run:</p> 127 + <pre><code>cargo build --release 128 + ./target/release/tbsp --help 129 + </code></pre> 130 + <p><code>tbsp</code> requires three inputs:</p> 131 + <ul> 132 + <li>a <code>tbsp</code> program, referred to as &quot;program file&quot; 133 + </li> 134 + <li>a language 135 + </li> 136 + <li>an input file or some input text at stdin 137 + </li> 138 + </ul> 139 + <p>You can run the interpreter like so (this program prints an 140 + overview of a rust file):</p> 141 + <pre><code>$ ./target/release/tbsp \ 142 + -f./examples/code-overview/overview.tbsp \ 143 + -l rust \ 144 + src/main.rs 145 + module 146 + └╴struct Cli 147 + └╴trait Cli 148 + └╴fn program 149 + └╴fn language 150 + └╴fn file 151 + └╴fn try_consume_stdin 152 + └╴fn main 153 + </code></pre> 154 + </div></main></div></body></html>
+318
_site/posts/lightweight_linting/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>lightweight linting</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">lightweight linting</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">lightweight linting</h1><div class="text-center space-x-2"><span>6 min</span><span>·</span><span>75 sentences</span><span>·</span><span>218.45 cm</span><span>·</span><span>26.01.2022</span></div><p><a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries">Tree-sitter</a> 3 + queries allow you to search for patterns in syntax trees, 4 + much like a regex would, in text. Combine that with some Rust 5 + glue to write simple, custom linters.</p> 6 + <h3 id="tree-sitter-syntax-trees">Tree-sitter syntax trees</h3> 7 + <p>Here is a quick crash course on syntax trees generated by 8 + tree-sitter. Syntax trees produced by tree-sitter are 9 + represented by S-expressions. The generated S-expression for 10 + the following Rust code,</p> 11 + <pre><code class="language-rust">fn main() { 12 + let x = 2; 13 + } 14 + </code></pre> 15 + <p>would be:</p> 16 + <pre><code class="language-scheme">(source_file 17 + (function_item 18 + name: (identifier) 19 + parameters: (parameters) 20 + body: 21 + (block 22 + (let_declaration 23 + pattern: (identifier) 24 + value: (integer_literal))))) 25 + </code></pre> 26 + <p>Syntax trees generated by tree-sitter have a couple of other 27 + cool properties: they are <em>lossless</em> syntax trees. Given a 28 + lossless syntax tree, you can regenerate the original source 29 + code in its entirety. Consider the following addition to our 30 + example:</p> 31 + <pre><code class="language-rust"> fn main() { 32 + + // a comment goes here 33 + let x = 2; 34 + } 35 + </code></pre> 36 + <p>The tree-sitter syntax tree preserves the comment, while the 37 + typical abstract syntax tree wouldn't:</p> 38 + <pre><code class="language-scheme"> (source_file 39 + (function_item 40 + name: (identifier) 41 + parameters: (parameters) 42 + body: 43 + (block 44 + + (line_comment) 45 + (let_declaration 46 + pattern: (identifier) 47 + value: (integer_literal))))) 48 + </code></pre> 49 + <h3 id="tree-sitter-queries">Tree-sitter queries</h3> 50 + <p>Tree-sitter provides a DSL to match over CSTs. These queries 51 + resemble our S-expression syntax trees, here is a query to 52 + match all line comments in a Rust CST:</p> 53 + <pre><code class="language-scheme">(line_comment) 54 + 55 + ; matches the following rust code 56 + ; // a comment goes here 57 + </code></pre> 58 + <p>Neat, eh? But don't take my word for it, give it a go on the 59 + <a href="https://tree-sitter.github.io/tree-sitter/playground">tree-sitter 60 + playground</a>. 61 + Type in a query like so:</p> 62 + <pre><code class="language-scheme">; the web playground requires you to specify a &quot;capture&quot; 63 + ; you will notice the capture and the nodes it captured 64 + ; turn blue 65 + (line_comment) @capture 66 + </code></pre> 67 + <p>Here's another to match <code>let</code> expressions that 68 + bind an integer to an identifier:</p> 69 + <pre><code class="language-scheme">(let_declaration 70 + pattern: (identifier) 71 + value: (integer_literal)) 72 + 73 + ; matches: 74 + ; let foo = 2; 75 + </code></pre> 76 + <p>We can <em>capture</em> nodes into variables:</p> 77 + <pre><code class="language-scheme">(let_declaration 78 + pattern: (identifier) @my-capture 79 + value: (integer_literal)) 80 + 81 + ; matches: 82 + ; let foo = 2; 83 + 84 + ; captures: 85 + ; foo 86 + </code></pre> 87 + <p>And apply certain <em>predicates</em> to captures:</p> 88 + <pre><code class="language-scheme">((let_declaration 89 + pattern: (identifier) @my-capture 90 + value: (integer_literal)) 91 + (#eq? @my-capture &quot;foo&quot;)) 92 + 93 + ; matches: 94 + ; let foo = 2; 95 + 96 + ; and not: 97 + ; let bar = 2; 98 + </code></pre> 99 + <p>The <code>#match?</code> predicate checks if a capture matches a regex:</p> 100 + <pre><code class="language-scheme">((let_declaration 101 + pattern: (identifier) @my-capture 102 + value: (integer_literal)) 103 + (#match? @my-capture &quot;foo|bar&quot;)) 104 + 105 + ; matches both `foo` and `bar`: 106 + ; let foo = 2; 107 + ; let bar = 2; 108 + </code></pre> 109 + <p>Exhibit indifference, as a stoic programmer would, with the 110 + <em>wildcard</em> pattern:</p> 111 + <pre><code class="language-scheme">(let_declaration 112 + pattern: (identifier) 113 + value: (_)) 114 + 115 + ; matches: 116 + ; let foo = &quot;foo&quot;; 117 + ; let foo = 42; 118 + ; let foo = bar; 119 + </code></pre> 120 + <p><a href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries">The 121 + documentation</a> 122 + does the tree-sitter query DSL more justice, but we now know 123 + enough to write our first lint.</p> 124 + <h3 id="write-you-a-tree-sitter-lint">Write you a tree-sitter lint</h3> 125 + <p>Strings in <code>std::env</code> functions are error prone:</p> 126 + <pre><code class="language-rust">std::env::remove_var(&quot;RUST_BACKTACE&quot;); 127 + // ^^^^ &quot;TACE&quot; instead of &quot;TRACE&quot; 128 + </code></pre> 129 + <p>I prefer this instead:</p> 130 + <pre><code class="language-rust">// somewhere in a module that is well spellchecked 131 + static BACKTRACE: &amp;str = &quot;RUST_BACKTRACE&quot;; 132 + 133 + // rest of the codebase 134 + std::env::remove_var(BACKTRACE); 135 + </code></pre> 136 + <p>Let's write a lint to find <code>std::env</code> functions that use 137 + strings. Put aside the effectiveness of this lint for the 138 + moment, and take a stab at writing a tree-sitter query. For 139 + reference, a function call like so:</p> 140 + <pre><code class="language-rust">remove_var(&quot;RUST_BACKTRACE&quot;) 141 + </code></pre> 142 + <p>Produces the following S-expression:</p> 143 + <pre><code class="language-scheme">(call_expression 144 + function: (identifier) 145 + arguments: (arguments (string_literal))) 146 + </code></pre> 147 + <p>We are definitely looking for a <code>call_expression</code>:</p> 148 + <pre><code class="language-scheme">(call_expression) @raise 149 + </code></pre> 150 + <p>Whose function name matches <code>std::env::var</code> or 151 + <code>std::env::remove_var</code> at the very least (I know, I know, 152 + this isn't the most optimal regex):</p> 153 + <pre><code class="language-scheme">((call_expression 154 + function: (_) @fn-name) @raise 155 + (#match? @fn-name &quot;std::env::(var|remove_var)&quot;)) 156 + </code></pre> 157 + <p>Let's turn that <code>std::</code> prefix optional:</p> 158 + <pre><code class="language-scheme">((call_expression 159 + function: (_) @fn-name) @raise 160 + (#match? @fn-name &quot;(std::|)env::(var|remove_var)&quot;)) 161 + </code></pre> 162 + <p>And ensure that <code>arguments</code> is a string:</p> 163 + <pre><code class="language-scheme">((call_expression 164 + function: (_) @fn-name 165 + arguments: (arguments (string_literal))) 166 + (#match? @fn-name &quot;(std::|)env::(var|remove_var)&quot;)) 167 + </code></pre> 168 + <h3 id="running-our-linter">Running our linter</h3> 169 + <p>We could always plug our query into the web playground, but 170 + let's go a step further:</p> 171 + <pre><code class="language-bash">cargo new --bin toy-lint 172 + </code></pre> 173 + <p>Add <code>tree-sitter</code> and <code>tree-sitter-rust</code> to your 174 + dependencies:</p> 175 + <pre><code class="language-toml"># within Cargo.toml 176 + [dependencies] 177 + tree-sitter = &quot;0.20&quot; 178 + 179 + [dependencies.tree-sitter-rust] 180 + git = &quot;https://github.com/tree-sitter/tree-sitter-rust&quot; 181 + </code></pre> 182 + <p>Let's load in some Rust code to work with. As <a href="https://en.wikipedia.org/wiki/Self-reference">an ode to 183 + Gödel</a> 184 + (G<code>ode</code>l?), why not load in our linter itself:</p> 185 + <pre><code class="language-rust">fn main() { 186 + let src = include_str!(&quot;main.rs&quot;); 187 + } 188 + </code></pre> 189 + <p>Most tree-sitter APIs require a reference to a <code>Language</code> 190 + struct, we will be working with Rust if you haven't 191 + already guessed:</p> 192 + <pre><code class="language-rust">use tree_sitter::Language; 193 + 194 + let rust_lang: Language = tree_sitter_rust::language(); 195 + </code></pre> 196 + <p>Enough scaffolding, let's parse some Rust:</p> 197 + <pre><code class="language-rust">use tree_sitter::Parser; 198 + 199 + let mut parser = Parser::new(); 200 + parser.set_language(rust_lang).unwrap(); 201 + 202 + let parse_tree = parser.parse(&amp;src, None).unwrap(); 203 + </code></pre> 204 + <p>The second argument to <code>Parser::parse</code> may be of interest. 205 + Tree-sitter has this cool feature that allows for quick 206 + reparsing of existing parse trees if they contain edits. If 207 + you do happen to want to reparse a source file, you can pass 208 + in the old tree:</p> 209 + <pre><code class="language-rust">// if you wish to reparse instead of parse 210 + old_tree.edit(/* redacted */); 211 + 212 + // generate shiny new reparsed tree 213 + let new_tree = parser.parse(&amp;src, Some(old_tree)).unwrap() 214 + </code></pre> 215 + <p>Anyhow (<a href="http://github.com/dtolnay/anyhow">hah!</a>), now that we have a parse tree, we can inspect it:</p> 216 + <pre><code class="language-rust">println!(&quot;{}&quot;, parse_tree.root_node().to_sexp()); 217 + </code></pre> 218 + <p>Or better yet, run a query on it:</p> 219 + <pre><code class="language-rust">use tree_sitter::Query; 220 + 221 + let query = Query::new( 222 + rust_lang, 223 + r#&quot; 224 + ((call_expression 225 + function: (_) @fn-name 226 + arguments: (arguments (string_literal))) @raise 227 + (#match? @fn-name &quot;(std::|)env::(var|remove_var)&quot;)) 228 + &quot;# 229 + ) 230 + .unwrap(); 231 + </code></pre> 232 + <p>A <code>QueryCursor</code> is tree-sitter's way of maintaining state as 233 + we iterate through the matches or captures produced by 234 + running a query on the parse tree. Observe:</p> 235 + <pre><code class="language-rust">use tree_sitter::QueryCursor; 236 + 237 + let mut query_cursor = QueryCursor::new(); 238 + let all_matches = query_cursor.matches( 239 + &amp;query, 240 + parse_tree.root_node(), 241 + src.as_bytes(), 242 + ); 243 + </code></pre> 244 + <p>We begin by passing our query to the cursor, followed by the 245 + &quot;root node&quot;, which is another way of saying, &quot;start from the 246 + top&quot;, and lastly, the source itself. If you have already 247 + taken a look at the C API, you will notice that the last 248 + argument, the source (known as the <code>TextProvider</code>), is not 249 + required. The Rust bindings seem to require this argument to 250 + provide predicate functionality such as <code>#match?</code> and 251 + <code>#eq?</code>.</p> 252 + <p>Do something with the matches:</p> 253 + <pre><code class="language-rust">// get the index of the capture named &quot;raise&quot; 254 + let raise_idx = query.capture_index_for_name(&quot;raise&quot;).unwrap(); 255 + 256 + for each_match in all_matches { 257 + // iterate over all captures called &quot;raise&quot; 258 + // ignore captures such as &quot;fn-name&quot; 259 + for capture in each_match 260 + .captures 261 + .iter() 262 + .filter(|c| c.idx == raise_idx) 263 + { 264 + let range = capture.node.range(); 265 + let text = &amp;src[range.start_byte..range.end_byte]; 266 + let line = range.start_point.row; 267 + let col = range.start_point.column; 268 + println!( 269 + &quot;[Line: {}, Col: {}] Offending source code: `{}`&quot;, 270 + line, col, text 271 + ); 272 + } 273 + } 274 + </code></pre> 275 + <p>Lastly, add the following line to your source code, to get 276 + the linter to catch something:</p> 277 + <pre><code class="language-rust">env::remove_var(&quot;RUST_BACKTRACE&quot;); 278 + </code></pre> 279 + <p>And <code>cargo run</code>:</p> 280 + <pre><code class="language-shell">λ cargo run 281 + Compiling toy-lint v0.1.0 (/redacted/path/to/toy-lint) 282 + Finished dev [unoptimized + debuginfo] target(s) in 0.74s 283 + Running `target/debug/toy-lint` 284 + [Line: 40, Col: 4] Offending source code: `env::remove_var(&quot;RUST_BACKTRACE&quot;)` 285 + </code></pre> 286 + <p>Thank you tree-sitter!</p> 287 + <h3 id="bonus">Bonus</h3> 288 + <p>Keen readers will notice that I avoided <code>std::env::set_var</code>. 289 + Because <code>set_var</code> is called with two arguments, a &quot;key&quot; and 290 + a &quot;value&quot;, unlike <code>env::var</code> and <code>env::remove_var</code>. As a 291 + result, it requires more juggling:</p> 292 + <pre><code class="language-scheme">((call_expression 293 + function: (_) @fn-name 294 + arguments: (arguments . (string_literal)? . (string_literal) .)) @raise 295 + (#match? @fn-name &quot;(std::|)env::(var|remove_var|set_var)&quot;)) 296 + </code></pre> 297 + <p>The interesting part of this query is the humble <code>.</code>, the 298 + <em>anchor</em> operator. Anchors help constrain child nodes in 299 + certain ways. In this case, it ensures that we match exactly 300 + two <code>string_literal</code>s who are siblings or exactly one 301 + <code>string_literal</code> with no siblings. Unfortunately, this query 302 + also matches the following invalid Rust code:</p> 303 + <pre><code class="language-rust">// remove_var accepts only 1 arg! 304 + std::env::remove_var(&quot;RUST_BACKTRACE&quot;, &quot;1&quot;); 305 + </code></pre> 306 + <h3 id="notes">Notes</h3> 307 + <p>All-in-all, the query DSL does a great job in lowering the 308 + bar to writing language tools. The knowledge gained from 309 + mastering the query DSL can be applied to other languages 310 + that have tree-sitter grammars too. This query 311 + detects <code>to_json</code> methods that do not accept additional 312 + arguments, in Ruby:</p> 313 + <pre><code class="language-scheme">((method 314 + name: (identifier) @fn 315 + !parameters) 316 + (#is? @fn &quot;to_json&quot;)) 317 + </code></pre> 318 + </div></main></div></body></html>
+129
_site/posts/lotus58/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>lotus58</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">lotus58</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">lotus58</h1><div class="text-center space-x-2"><span>3 min</span><span>·</span><span>42 sentences</span><span>·</span><span>68.58 cm</span><span>·</span><span>13.06.2022</span></div><p>Earlier this month, I decided that I would laugh at Indian 3 + customs in the face by building a split-ergo mechanical 4 + keyboard from scratch rather than purchasing a Moonlander.</p> 5 + <p><img src="https://cdn.oppi.li/i8k.jpg" alt="The finished product" /></p> 6 + <h2 id="sourcing-the-parts">Sourcing the parts</h2> 7 + <p>If you, like me, live in India, you might find this section 8 + useful. My approach to finding parts:</p> 9 + <ul> 10 + <li>Check reputed, local online stores 11 + </li> 12 + <li>Check physical hardware stores 13 + </li> 14 + <li>Import the part 15 + </li> 16 + </ul> 17 + <h3 id="pcbs">PCBs</h3> 18 + <p>This was by far the hardest component to procure. 19 + Fabrication services have certain <em>capabilities</em>. 20 + Capabilities are the limitations of a fabrication service. 21 + For example, a service may be capable of drilling holes no 22 + smaller than 0.3mm in diameter. Most sites have a 23 + verification process to check if their capabilities meet 24 + your design's requirements. I tried a few local PCB 25 + fabrication services:</p> 26 + <ul> 27 + <li>Lion PCB: Capabilities did not meet my requirements 28 + </li> 29 + <li>PCBPower: Capabilities did not meet my requirements 30 + </li> 31 + <li>Circuitwala: Capabilities did not meet my requirements 32 + </li> 33 + </ul> 34 + <p>I settled for JLCPCB, a Chinese service. PCBs themselves 35 + were 16 USD, shipping was another 35 USD, and customs was 36 + another 3.5K INR (ouch).</p> 37 + <h3 id="case-material">Case material</h3> 38 + <p>I don't really have a case for the Lotus58, it is more of a 39 + &quot;plastic sandwich&quot;. I purchased acrylic plates from Robu. 40 + Cheap, fast, solid, and customizable. I cannot recommend 41 + Robu enough. A full set of plates (2 top plates and 2 bottom 42 + plates) cost me about 500 INR. I also bought a pair of 43 + laptop height raisers on Amazon to create a budget tenting 44 + setup.</p> 45 + <h3 id="electronics">Electronics</h3> 46 + <p>You'll need a few rather specific electronic components such 47 + as hotswap sockets and TRRS mounts, the rest are commonly 48 + available:</p> 49 + <ul> 50 + <li>Hotswap sockets: StacksKB 51 + </li> 52 + <li>TRRS mounts (PJ 320A): StacksKB 53 + </li> 54 + <li>Diodes (1N4841): StacksKB 55 + </li> 56 + <li>M2 screws: StacksKB 57 + </li> 58 + <li>M2 spacers: ThinkRobotics 59 + </li> 60 + <li>Arduino Pro Micro: Robu 61 + </li> 62 + </ul> 63 + <p>I skimped out on optional components such as OLEDs and 64 + rotary encoders.</p> 65 + <h3 id="switches-and-keycaps">Switches and Keycaps</h3> 66 + <p>Arguably the most fun part of the build:</p> 67 + <ul> 68 + <li>Gateron Oil King switches: Rectangles 69 + </li> 70 + <li>DSA blanks: Meckeys 71 + </li> 72 + </ul> 73 + <h2 id="building-the-keyboard">Building the keyboard</h2> 74 + <p>The the build is extremely straightforward. Through hole 75 + components are easy to solder. Be wary of component 76 + placement and orientation. Check thrice, solder once. Few 77 + debugging tips:</p> 78 + <ul> 79 + <li>if a single key does not actuate, check the hotswap for 80 + poor soldering (reflow the joint), and the switch pins for 81 + deformation during installation 82 + </li> 83 + <li>if an entire column or row activates on a single 84 + key-press, a connection has been shorted 85 + </li> 86 + <li>if only some of the keys on a given row actuate, a diode 87 + has been soldered on the wrong way 88 + </li> 89 + </ul> 90 + <h2 id="the-typing-experience">The typing experience</h2> 91 + <p>I decidede to give QWERTY the boot and learn Colemak along 92 + with the new keyboard. The first few weeks were terrible 93 + because I could neither type QWERTY nor Colemak, but I got 94 + the hang of it pretty quickly. Typing websites do help, but 95 + it is best to simply use it in your daily workflow. No site 96 + can help you get accustomed to the various things you use 97 + your keyboard for such as switching windows or navigating 98 + vim/tmux.</p> 99 + <h3 id="colemak">Colemak</h3> 100 + <p>Alt layouts such as Colemak are definitely worth it. I find 101 + that Colemak reduces finger movement a lot, a good portion 102 + of the keys on the left hand are the same as QWERTY, it 103 + is fairly easy to pick up as well.</p> 104 + <h3 id="vim">Vim</h3> 105 + <p>Using an alt layout means most programs with keyboard 106 + shortcuts are not going to work as expected, <code>HJKL</code> on vim for 107 + movements being one of them. I took the short route out by 108 + creating a new layer with arrow keys on the home row:</p> 109 + <pre><code>default homerow: 110 + H N E I 111 + 112 + &quot;nav&quot; layer: 113 + &lt; v ^ &gt; 114 + </code></pre> 115 + <p>The remaining commands in vim are largely mnemonics. 116 + Navigating with home-row arrow keys also means that I can 117 + use &quot;HJKL&quot; globally, to scroll a website, for example.</p> 118 + <h3 id="cutting-down-to-34-keys">Cutting down to 34 keys</h3> 119 + <p>A couple months into my ergo journey, I realized that I 120 + could get away by moving my fingers even lesser. I moved 121 + modifiers such as <code>Super</code>, <code>Ctrl</code>, <code>Alt</code> and <code>Shift</code> to the 122 + home-row as QMK Mod Taps. The rest of the keys are cleverly 123 + placed in layers, not too much unlike the Miryoku layout. 124 + Even for someone that writes Rust (a symbol-heavy grammar), 125 + I find 34-keys to be sufficient.</p> 126 + <h2 id="conclusion">Conclusion</h2> 127 + <p>I have been bitten by the ergomech bug.</p> 128 + <p><img src="https://cdn.oppi.li/XM3.jpg" alt="The Lotus58 in action" /></p> 129 + </div></main></div></body></html>
+141
_site/posts/mounting_the_atmosphere/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>mounting the atmosphere</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">mounting the atmosphere</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">mounting the atmosphere</h1><div class="text-center space-x-2"><span>3 min</span><span>·</span><span>179 sentences</span><span>·</span><span>76.71 cm</span><span>·</span><span>31.07.2025</span></div><p><a href="https://tangled.sh/@oppi.li/pdsfs">pdsfs</a> is a tool that 3 + mounts <a href="https://atproto.com">atproto</a> PDS repositories as a 4 + <a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace">FUSE</a> 5 + filesystem.</p> 6 + <p>A <a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace">PDS 7 + repository</a> 8 + contains all data published by a user to the atmosphere. It 9 + is exportable as a CAR (content-addressable archive) file. 10 + pdsfs is a tool that mounts this CAR file as a readonly-FUSE 11 + filesystem, allowing quick and easy exploration.</p> 12 + <p>To motivate the need for such a program, we could begin by 13 + mounting a repository:</p> 14 + <pre><code>λ pdsfs oppi.li 15 + mounted at &quot;mnt&quot; 16 + hit enter to unmount and exit... 17 + </code></pre> 18 + <p>oppi.li is my handle in the atmosphere. The tool does some 19 + hardwork to determine the location of my PDS repository 20 + given my handle, but that is not important. Let's have a 21 + look around:</p> 22 + <pre><code>λ ls mnt/ 23 + did:plc:qfpnj4og54vl56wngdriaxug/ 24 + </code></pre> 25 + <p>The <code>did:plc:stuff</code> is my 26 + <a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace">DID</a>. 27 + Digging deeper:</p> 28 + <pre><code>λ ls mnt/did\:plc\:qfpnj4og54vl56wngdriaxug/ 29 + app.bsky.actor.profile/ place.stream.chat.message/ 30 + app.bsky.actor.status/ place.stream.chat.profile/ 31 + app.bsky.feed.generator/ place.stream.key/ 32 + app.bsky.feed.like/ place.stream.livestream/ 33 + app.bsky.feed.post/ sh.tangled.actor.profile/ 34 + app.bsky.feed.repost/ sh.tangled.feed.reaction/ 35 + app.bsky.graph.block/ sh.tangled.feed.star/ 36 + app.bsky.graph.follow/ sh.tangled.graph.follow/ 37 + app.rocksky.album/ sh.tangled.knot/ 38 + app.rocksky.artist/ sh.tangled.knot.member/ 39 + . 40 + . 41 + . 42 + </code></pre> 43 + <p>We have some data from the repository now. These are 44 + &quot;collections&quot;. If I want to publish a post to Bluesky, I 45 + would write content to the <code>app.bsky.feed.post</code> collection 46 + in my PDS. This will then be indexed by a Bluesky appview 47 + (such as <a href="https://bsky.app">bsky.app</a> or 48 + <a href="https://zeppelin.social">zeppelin.social</a>) and show up 49 + under my profile there.</p> 50 + <p>pdsfs is kind enough to deserialize the in the PDS 51 + repository (stored as CBOR normally) to JSON on the 52 + filesystem:</p> 53 + <pre><code>λ cat sh.tangled.repo/3ljidbevrjh22 | jq 54 + { 55 + &quot;$type&quot;: &quot;sh.tangled.repo&quot;, 56 + &quot;addedAt&quot;: &quot;2025-03-03T16:04:13Z&quot;, 57 + &quot;knot&quot;: &quot;knot1.tangled.sh&quot;, 58 + &quot;name&quot;: &quot;hello-world&quot;, 59 + &quot;owner&quot;: &quot;did:plc:3danwc67lo7obz2fmdg6jxcr&quot; 60 + } 61 + </code></pre> 62 + <p>Thanks pdsfs!</p> 63 + <p>I publish my music listening habits to my PDS to the 64 + <code>app.rocksky.scrobble</code> collection, because 65 + <a href="https://rocksky.app">Rocksky</a> recognizes and indexes this 66 + collection. I have wired up my personal navidrome instance 67 + to write data of this form into my PDS everytime I listen to 68 + a track. Here are my top artists in order:</p> 69 + <pre><code>λ jq -r '.artist' app.rocksky.scrobble/* | sort | uniq -c | sort -nr 70 + 117 Thank You Scientist 71 + 45 FKJ 72 + 34 Covet 73 + 33 VOLA 74 + 23 Sam Cooke 75 + 22 Dark Tranquillity 76 + 21 Piero Piccioni 77 + 12 Bloodywood 78 + 11 Frank Sinatra 79 + 10 Dream Theater 80 + </code></pre> 81 + <p>It is true, I love Sam Cooke.</p> 82 + <p>pdsfs allows mounting multiple repositories at a time. Allow 83 + me to introduce my friends:</p> 84 + <pre><code>λ pdsfs icyphox.sh anil.recoil.org steveklabnik.com tangled.sh 85 + using cached CAR file for...did:plc:hwevmowznbiukdf6uk5dwrrq 86 + using cached CAR file for...did:plc:nhyitepp3u4u6fcfboegzcjw 87 + download complete for...did:plc:3danwc67lo7obz2fmdg6jxcr 88 + download complete for...did:plc:wshs7t2adsemcrrd4snkeqli 89 + mounted at &quot;mnt&quot; 90 + hit enter to unmount and exit... 91 + 92 + # -- in a separate shell -- 93 + 94 + λ cat ./mnt/*/app.bsky.actor.profile/* \ 95 + | jq -r '&quot;\(.displayName)\n\(.description)\n---&quot;' \ 96 + | sed '/^$/d' 97 + Steve Klabnik 98 + #rustlang, #jj-vcs, atproto, shitposts, urbanism. I 99 + contain multitudes. Working on #ruelang but just for 100 + fun. Currently in Austin, TX, but from Pittsburgh. 101 + Previously in Bushwick, the Mission, LA. 102 + --- 103 + Anirudh Oppiliappan 104 + building @tangled.sh — code collaboration platform built 105 + on atproto helsinki, finland · https://anirudh.fi · 106 + (somewhat) effective altruist 107 + --- 108 + Anil Madhavapeddy 109 + Professor of Planetary Computing at the University of 110 + Cambridge @cst.cam.ac.uk, where I co-lead the 111 + @eeg.cl.cam.ac.uk, and am also to found at 112 + @conservation.cam.ac.uk. Homepage at 113 + https://anil.recoil.org 114 + --- 115 + Tangled 116 + https://tangled.sh is a git collaboration platform built 117 + on atproto. Social coding, but for real this time! 118 + Discord: chat.tangled.sh IRC: #tangled @ libera.chat 119 + Built by @oppi.li &amp; @icyphox.sh 120 + --- 121 + </code></pre> 122 + <p>All my friends use <a href="https://tangled.sh">tangled.sh</a>, which 123 + requires them to publish their ssh public key to their PDii 124 + (PDSes?). Perhaps I would like to add their keys to my 125 + allowed_signers file to verify their commit signatures:</p> 126 + <pre><code>λ for dir in ./*/sh.tangled.publicKey; 127 + do cat $dir/$(ls -r $dir | head -n1) | jq -r '.key'; 128 + done | tee allowed_signers 129 + ssh-rsa AAAAB3NzaC1yc2EAAA...dHPqc= steveklabnik@DESKTOP-VV370NK 130 + ssh-ed25519 AAAAC3NzaC1lZD...g9bAdk icy@wyndle 131 + ssh-ed25519 AAAAC3NzaC1lZD...BqlM1u anil@recoil.org 132 + </code></pre> 133 + <p>FUSE is quite liberating in that it allows you to represent 134 + anything as a filesystem. When applications like <code>ls</code> and 135 + <code>cat</code> are executed, the syscalls to <code>open</code> and <code>read</code> are 136 + rerouted to your custom fs implementation (pdsfs in this 137 + case). The custom fs implementation is free to as it 138 + pleases, in fact the first iteration of pdsfs accessed the 139 + network for each open/read call to fetch live data from 140 + PDii.</p> 141 + </div></main></div></body></html>
+23
_site/posts/my_setup/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>my setup</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">my setup</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">my setup</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>17 sentences</span><span>·</span><span>13.21 cm</span><span>·</span><span>06.11.2019</span></div><p>Decided to do one of these because everyone does one of 3 + these.</p> 4 + <p><img src="https://cdn.oppi.li/Hb.png" alt="" /></p> 5 + <p>My entire setup is managed with GNU <code>stow</code>, making it easier 6 + to replicate on fresh installations. You can find my 7 + configuration files on <a href="https://github.com/nerdypepper">GitHub</a>.</p> 8 + <p>I run Void Linux (glibc) on my 9 + <a href="https://store.hp.com/us/en/mdp/laptops/envy-13">HP Envy 13&quot; (2018)</a>. 10 + To keep things simple, I run a raw X session with <code>2bwm</code> as my 11 + window manager, along with <code>dunst</code> (notification daemon) and 12 + Sam's <a href="https://github.com/sdhand/compton"><code>compton</code></a> 13 + (compositor) fork.</p> 14 + <p>I am a fan of GNU tools, so I use <code>bash</code> as my shell, and 15 + <code>coreutils</code> to manage files, archives, strings, paths etc. I 16 + edit files with <code>vim</code>, chat with <code>weechat</code>, listen to music 17 + with <code>cmus</code>, monitor processes with <code>htop</code>, manage sessions 18 + with <code>tmux</code>, read pdfs in <code>zathura</code>. I rarely ever leave 19 + the comfort of my terminal emulator, <code>urxvt</code>.</p> 20 + <p>Most of my academic typesetting is done with TeX, and 21 + compiled with <code>xelatex</code>. Other <em>fun</em> documents are made with 22 + GIMP :).</p> 23 + </div></main></div></body></html>
+76
_site/posts/nixOS/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>nixOS</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">nixOS</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">nixOS</h1><div class="text-center space-x-2"><span>2 min</span><span>·</span><span>55 sentences</span><span>·</span><span>47.76 cm</span><span>·</span><span>01.09.2020</span></div><p>I have been eyeing operating systems with functional package 3 + managers for a while now, aka, NixOS or Guix. Reproducible 4 + builds, declarative and rollback-able system configuration, 5 + system consistency, all sound pretty cool. I have been using 6 + NixOS for about a month now.</p> 7 + <h3 id="installation">Installation</h3> 8 + <p>I went with their minimal installation ISO. The installation 9 + was pretty smooth from start to end, no hitches there. The 10 + entire <a href="https://nixos.org/manual/nixos/stable/">manual</a> is 11 + available offline, and is accessible during the 12 + installation. Very handy.</p> 13 + <h3 id="setup">Setup</h3> 14 + <p>The entire system is configured via 15 + <code>/etc/nixos/configuration.nix</code>. Wifi, <code>libinput</code> gestures, 16 + audio, locale settings, there are options for literally 17 + everything. You can declaratively write down the packages 18 + you want installed too. With fresh installs of most distros, 19 + I usually fumble with getting things like screen backlight 20 + and media keys to work. If I do manage to fix it, I can't 21 + carry it forward to future installations trivially. Getting 22 + all my hardware to work on NixOS is as easy as:</p> 23 + <pre><code>{ 24 + server.xserver.libinput.enable = true; # touchpad 25 + programs.light.enable = true; # backlight 26 + hardware.pulseaudio.enable = true; # audio 27 + networking.wireless.enable = true; # wifi 28 + } 29 + </code></pre> 30 + <h3 id="developing-with-nix">Developing with Nix</h3> 31 + <p>Nix makes it easy to enter environments that aren't affected 32 + by your system configuration using <code>nix-shell</code>.</p> 33 + <p>Builds may be generated by specifying a <code>default.nix</code> file, 34 + and running <code>nix-build</code>. Conventional package managers 35 + require you to specify a dependency list, but there is no 36 + guarantee that this list is complete. The package will build 37 + on your machine even if you forget a dependency. However, 38 + with Nix, packages are installed to <code>/nix/store</code>, and not 39 + global paths such as <code>/usr/bin/...</code>, if your project builds, 40 + it means you have included every last one.</p> 41 + <p>Issues on most my projects have been &quot;unable to build 42 + because <code>libxcb</code> is missing&quot;, or &quot;this version of <code>openssl</code> 43 + is too old&quot;. Tools like <code>cargo</code> and <code>pip</code> are poor package 44 + managers. While they <em>can</em> guarantee that Rust or Python 45 + dependencies are met, they make assumptions about the 46 + target system.</p> 47 + <p>For example, <a href="https://github.com/nerdypepper/site">this 48 + website</a> is now built 49 + using Nix, anyone using Nix may simply, clone the repository 50 + and run <code>./generate.sh</code>, and it would <em>just work</em>, while 51 + keeping your global namespace clean™:</p> 52 + <pre><code class="language-bash">#! /usr/bin/env nix-shell 53 + #! nix-shell -i bash -p eva pandoc esh 54 + 55 + # some bash magic ;) 56 + </code></pre> 57 + <p>Dependencies are included with the <code>-p</code> flag, the shell 58 + script is executed with an interpreter, specified with the 59 + <code>-i</code> flag.</p> 60 + <h3 id="impressions">Impressions</h3> 61 + <p>NixOS is by no means, simple. As a newcomer, using Nix was 62 + not easy, heck, I had to learn a purely functional, lazy 63 + language to just build programs. There is a lot to be 64 + desired on the tooling front as well. A well fleshed out LSP 65 + plugin would be nice (<a href="https://github.com/nix-community/rnix-lsp">rnix-lsp looks 66 + promising</a>).</p> 67 + <p>Being able to rollback changes at a system level is cool. 68 + Package broke something? Just <code>nixos-rebuild switch --rollback</code>! Deleted <code>nix</code> by mistake? Find the binary in 69 + <code>/nix/store</code> and rollback! You aren't punished for not 70 + thinking twice.</p> 71 + <p>I don't see myself switching to anything else in the near 72 + future, NixOS does a lot of things right. If I ever need to 73 + reinstall NixOS, I can generate an <a href="https://github.com/nix-community/nixos-generators">image of my current 74 + system</a>.</p> 75 + <p><a href="https://cdn.oppi.li/6m.png"><img src="https://cdn.oppi.li/6m.png" alt="" /></a></p> 76 + </div></main></div></body></html>
+191
_site/posts/novice_nix:_flake_templates/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>novice nix: flake templates</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">novice nix: flake templates</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">novice nix: flake templates</h1><div class="text-center space-x-2"><span>4 min</span><span>·</span><span>61 sentences</span><span>·</span><span>117.86 cm</span><span>·</span><span>05.10.2021</span></div><p>Flakes are very handy to setup entirely pure, 3 + project-specific dependencies (not just dependencies, but 4 + build steps, shell environments and more) in a declarative 5 + way. Writing Flake expressions can get repetitive though, 6 + oftentimes, you'd much rather start off with a skeleton. 7 + Luckily, <code>nix</code> already supports templates!</p> 8 + <p>You might already be familiar with <code>nix flake init</code>, that 9 + drops a &quot;default&quot; flake expression into your current working 10 + directory. If you head over to the manpage:</p> 11 + <pre><code class="language-bash">nix flake init --help 12 + </code></pre> 13 + <p>You will read that <code>nix flake init</code> creates a flake using 14 + the &quot;default template&quot;. Additionally, you can create a flake 15 + from a specific template by passing the <code>-t</code> flag. Where 16 + does this default originate from?</p> 17 + <h2 id="flake-registries">Flake Registries</h2> 18 + <p>Quick detour into registries! Registries are a way to alias 19 + popular flakes using identifiers:</p> 20 + <pre><code class="language-bash"># list a few predefined registries 21 + $ nix registry list 22 + . . . 23 + global flake:nixpkgs github:NixOS/nixpkgs 24 + global flake:patchelf github:NixOS/patchelf 25 + global flake:nix-serve github:edolstra/nix-serve 26 + global flake:templates github:NixOS/templates 27 + global flake:nickel github:tweag/nickel 28 + . . . 29 + 30 + # you can do 31 + $ nix flake show nickel 32 + 33 + # instead of 34 + $ nix flake show github:tweag/nickel 35 + 36 + # which is short for 37 + $ nix flake show git+https://github.com/tweag/nickel 38 + </code></pre> 39 + <p>You might notice a registry called <code>templates</code> aliased to 40 + <code>github:NixOS/templates</code>. Take a peek with <code>nix flake show</code>:</p> 41 + <pre><code class="language-bash">$ nix flake show templates 42 + github:NixOS/templates/79f48a7b822f35c068c5e235da2e9fbd154cecee 43 + ├───defaultTemplate: template: A very basic flake 44 + └───templates 45 + ├───bash-hello: template: An over-engineered Hello World in bash 46 + ├───c-hello: template: An over-engineered Hello World in C 47 + ├───rust-web-server: template: A Rust web server including a NixOS module 48 + ├───simpleContainer: template: A NixOS container running apache-httpd 49 + └───trivial: template: A very basic flake 50 + </code></pre> 51 + <p>Aha! There is a flake output called <code>defaultTemplate</code>. This 52 + is the template being sourced when you run <code>nix flake init</code>. 53 + Astute readers may conclude the following:</p> 54 + <pre><code class="language-bash">$ nix flake init 55 + 56 + # is equivalent to 57 + $ nix flake init -t templates#defaultTemplate 58 + 59 + # is equivalent to 60 + $ nix flake init -t github:NixOS/templates#defaultTemplate 61 + 62 + # which is short for 63 + $ nix flake init -t git+https://github.com/NixOS/templates#defaultTemplate 64 + </code></pre> 65 + <p>Similarly, the other templates can be accessed via:</p> 66 + <pre><code class="language-bash">$ nix flake init -t templates#c-hello 67 + $ nix flake init -t templates#simpleContainer 68 + # I think you get the drift ... 69 + </code></pre> 70 + <h2 id="rolling-your-own-templates">Rolling your own templates</h2> 71 + <p>Alright, so all we need to do is:</p> 72 + <ul> 73 + <li>create a flake with a <code>templates</code> output 74 + </li> 75 + <li>populate our template directories with content 76 + </li> 77 + <li>(<strong>optionally</strong>) alias our custom templates flake to an 78 + identifier using registries, for easier access 79 + </li> 80 + </ul> 81 + <p>Start off by creating a directory to store your templates in 82 + (we will be converting this to a registry later):</p> 83 + <pre><code class="language-bash">$ mkdir ~/mytemplates 84 + </code></pre> 85 + <p>A flake that exposes a &quot;template&quot; as its output looks 86 + something like this:</p> 87 + <pre><code class="language-nix"># inside ~/mytemplates/flake.nix 88 + 89 + { 90 + description = &quot;Pepper's flake templates&quot;; 91 + 92 + outputs = { self, ... }: { 93 + templates = { 94 + latex-report = { 95 + path = ./latex-report-template; 96 + description = &quot;A latex whitepaper project&quot;; 97 + }; 98 + rust-hello = { 99 + path = ./rust-hello-template; 100 + description = &quot;Simple Hello World in Rust&quot;; 101 + }; 102 + }; 103 + }; 104 + } 105 + </code></pre> 106 + <p>The <code>path</code> attribute to each template is what gets copied 107 + over when you initialize a flake. Running <code>nix flake init -t .#latex-report</code> will initialize the current directory with 108 + the contents of <code>./latex-report-template</code> (we are yet to 109 + populate these directories).</p> 110 + <p>The output of <code>nix flake show</code> should be something like:</p> 111 + <pre><code class="language-bash">$ nix flake show 112 + path:/home/np/code/nix-stuff/template-tests?narHash=sha256-{...} 113 + └───templates 114 + ├───latex-report: template: A latex whitepaper project 115 + └───rust-hello: template: Simple Hello World in Rust 116 + </code></pre> 117 + <p>Populate your template directories with content, here are my 118 + template directories for example:</p> 119 + <pre><code class="language-bash">$ tree mytemplates 120 + mytemplates/ 121 + ├── flake.nix 122 + ├── latex-report-template 123 + │   ├── flake.nix 124 + │   ├── makefile 125 + │   └── src 126 + │   ├── meta.sty 127 + │   └── report.tex 128 + └── rust-hello-template 129 + ├── Cargo.toml 130 + ├── flake.nix 131 + └── src 132 + └── main.rs 133 + </code></pre> 134 + <p>And that's it! Start using your templates with:</p> 135 + <pre><code class="language-bash">$ nix flake init -t ~/mytemplates#rust-hello 136 + $ tree . 137 + . 138 + ├── Cargo.toml 139 + ├── flake.nix 140 + └── src 141 + └── main.rs 142 + </code></pre> 143 + <p>To avoid writing <code>~/mytemplates</code> each time, simply alias it 144 + to a registry:</p> 145 + <pre><code class="language-bash"># alias it to `biscuits` 146 + $ nix registry add biscuits ~/mytemplates 147 + 148 + # you will see it listed under `user` registries 149 + $ nix registry list 150 + . . . 151 + user flake:biscuits path:/home/np/template-tests 152 + . . . 153 + 154 + $ nix flake init -t biscuits#latex-report 155 + </code></pre> 156 + <h2 id="extending-the-official-templates">Extending the official templates</h2> 157 + <p>I personally, would like the <code>biscuits</code> registry to include 158 + not just my homemade templates, but also the templates from 159 + <code>NixOS/templates</code> (and maybe a couple of other repositories 160 + in the wild):</p> 161 + <pre><code class="language-nix"> { 162 + description = &quot;Pepper's flake templates&quot;; 163 + 164 + + inputs = { 165 + + official-templates.url = github:NixOS/templates; 166 + + other-templates.url = github:some-other/templates; 167 + + }; 168 + 169 + outputs = { self, official-templates, other-templates ... }: { 170 + 171 + templates = { 172 + latex-report = { 173 + path = ./latex-report-template; 174 + description = &quot;A latex whitepaper project&quot;; 175 + }; 176 + rust-hello = { 177 + path = ./rust-hello-template; 178 + description = &quot;Simple Hello World in Rust, with overloaded Rust toolchain&quot;; 179 + }; 180 + } 181 + + // official-templates.templates 182 + + // other-templates.templates; 183 + 184 + }; 185 + } 186 + </code></pre> 187 + <p>Running <code>nix flake show biscuits</code> will now list templates 188 + from the <code>biscuits</code> registry as well as the ones from 189 + <code>NixOS/templates</code>. Ensure that the names don't collide 190 + though.</p> 191 + </div></main></div></body></html>
+25
_site/posts/onivim_sucks/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>onivim sucks</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">onivim sucks</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">onivim sucks</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>26 sentences</span><span>·</span><span>17.27 cm</span><span>·</span><span>02.08.2019</span></div><p><a href="https://v2.onivim.io">Onivim</a> is a 'modern modal editor', combining fancy 3 + interface and language features with vim-style modal editing. What's wrong you 4 + ask?</p> 5 + <p>Apart from <a href="https://github.com/onivim/oni2/issues/550">buggy syntax highlighting</a>, 6 + <a href="https://github.com/onivim/oni2/issues/519">broken scrolling</a> and 7 + <a href="https://github.com/onivim/oni2/issues?q=is%3Aissue+label%3A%22daily+editor+blocker%22+is%3Aopen">others</a>, 8 + Onivim is <strong>proprietary</strong> software. It is licensed under a commercial 9 + <a href="https://github.com/onivim/oni1/blob/master/Outrun-Labs-EULA-v1.1.md">end user agreement license</a>, 10 + which prohibits redistribution in both object code and source code formats.</p> 11 + <p>Onivim's core editor logic (bits that belong to vim), have been separated from 12 + the interface, into <a href="https://github.com/onivim/libvim">libvim</a>. libvim is 13 + licensed under MIT, which means, this 'extension' of vim is perfectly in 14 + adherence to <a href="http://vimdoc.sourceforge.net/htmldoc/uganda.html#license">vim's license text</a>! 15 + Outrun Labs are exploiting this loophole (distributing vim as a library) to 16 + commercialize Onivim.</p> 17 + <p>Onivim's source code is available on <a href="https://github.com/onivim/oni2">GitHub</a>. 18 + They do mention that the source code trickles down to the 19 + <a href="https://github.com/onivim/oni2-mit">oni2-mit</a> repository, which (not yet) contains 20 + MIT-licensed code, <strong>18 months</strong> after each commit to the original repository.</p> 21 + <p>Want to contribute to Onivim? Don't. They make a profit out of your contributions. 22 + Currently, Onivim is priced at $19.99, 'pre-alpha' pricing which is 80% off the 23 + final price! If you are on the lookout for an editor, I would suggest using 24 + <a href="https://vim.org">Vim</a>, charity ware that actually works, and costs $100 lesser.</p> 25 + </div></main></div></body></html>
+104
_site/posts/pixel_art_in_GIMP/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>pixel art in GIMP</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">pixel art in GIMP</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">pixel art in GIMP</h1><div class="text-center space-x-2"><span>3 min</span><span>·</span><span>70 sentences</span><span>·</span><span>70.62 cm</span><span>·</span><span>08.04.2020</span></div><p>I've always been an admirer of pixel art, because of it's 3 + simplicity and it's resemblance to bitmap font design. 4 + Recently, I decided to take the dive and make some art of my 5 + own.</p> 6 + <p>I used GIMP because I am fairly familiar with it. Aseprite 7 + seems to be the editor of choice for animated pixel art 8 + though.</p> 9 + <h3 id="setting-up-the-canvas">Setting up the canvas</h3> 10 + <p>Picking a canvas size is daunting. Too small, and you won't 11 + be able to fit in enough detail to make a legible piece. Too 12 + big and you've got too many pixels to work with!</p> 13 + <p>I would suggest starting out with anywhere between 100x100 14 + and 200x200. <a href="https://cdn.oppi.li/u9.png">Here's</a> a sample 15 + configuration.</p> 16 + <p>Sometimes I use a 10x10 grid, <code>View &gt; Show Grid</code> and <code>Edit &gt; Preferences &gt; Default Grid &gt; Spacing</code>, but that can get 17 + jarring, so I throw down a couple of guides, drag right or 18 + down from the left or top gutters for vertical and 19 + horizontal guides respectively.</p> 20 + <h3 id="choosing-a-brush">Choosing a Brush</h3> 21 + <p>The most important part of our setup is the brush. Use the 22 + Pencil Tool (<code>n</code> on the keyboard) for hard edge drawings. 23 + Here's a small comparison if you don't know the difference 24 + between a hard edge and a soft edge:</p> 25 + <p><img src="https://cdn.oppi.li/kz.png" alt="Hard edge vs Soft Edge" /></p> 26 + <p>I turn the size down all the way to 1 (<code>[</code> on the keyboard). 27 + Set <code>Dynamics</code> off. <a href="https://cdn.oppi.li/Fs.png">Here's</a> a 28 + sample brush configuration.</p> 29 + <h3 id="laying-down-the-pixels">Laying down the pixels!</h3> 30 + <p>With the boring stuff out of the way, we can start with our 31 + piece. I usually follow a three step process:</p> 32 + <ul> 33 + <li>draw a rough outline 34 + </li> 35 + <li>fill in the shadows 36 + </li> 37 + <li>add highlights 38 + </li> 39 + </ul> 40 + <p>But this process is better explained with an example: an 41 + onigiri. Let us start off with a 100x100 canvas.</p> 42 + <h4 id="drawing-the-outline">Drawing the outline</h4> 43 + <p>For the most part, our figure will be symmetric. If you are 44 + on GIMP 2.10+, you can take advantage of the Symmetry 45 + Painting feature. Go ahead and enable vertical symmetry, 46 + <code>Window &gt; Dockable Dialogs &gt; Symmetry Painting</code> and 47 + <code>Symmetry Painting &gt; Symmetry &gt; Mirror &gt; Vertical</code>.</p> 48 + <p>If you are running an older version of GIMP, draw in the 49 + left side, duplicate the layer, flip it horizontally, and 50 + merge it with the original.</p> 51 + <p>Your outline might look something like this:</p> 52 + <p><img src="https://cdn.oppi.li/mn.png" alt="" /></p> 53 + <p>Go ahead and fill it in with the fill tool (<code>Shift + b</code> on 54 + the keyboard), add in some seaweed as well, preferably on a 55 + different layer. You can toggle symmetry on and off to save 56 + yourself some time.</p> 57 + <p><img src="https://cdn.oppi.li/xu.png" alt="" /></p> 58 + <h4 id="shadows">Shadows</h4> 59 + <p>For now, let us focus on the shadows on the object itself, 60 + we'll come back to the shadows cast by the object on the 61 + surface later.</p> 62 + <p>Shadows on any surface always follow the shape of the 63 + surface. A spherical onigiri would have a circular shadow:</p> 64 + <p><img src="https://cdn.oppi.li/FU.png" alt="" /></p> 65 + <p>A couple of noticeable changes:</p> 66 + <p><strong>Layers</strong>: The layer containing the seaweed has been hidden.<br /> 67 + <strong>Color</strong>: The color of the shadow is just a slightly 68 + lighter version of the original object (reduce the Value on 69 + the HSV scale).<br /> 70 + <strong>Area</strong>: The shadow does not go all the way (notice the bottom 71 + edges).</p> 72 + <p>The shadow does not go all the way because we will be 73 + filling in that area with another, darker shadow! An image 74 + might explain better:</p> 75 + <p><img src="https://cdn.oppi.li/Br.png" alt="" /></p> 76 + <p>To emulate soft lights, reduce the value by 2 to 3 points 77 + every iteration. Notice how area <code>1</code> is much larger than 78 + area <code>4</code>. This is because an onigiri resembles a bottom 79 + heavy oblate spheroid, a sphere that is slightly fatter 80 + around the lower bottom, and areas <code>1</code> and <code>2</code> catch more 81 + light than areas <code>3</code> and <code>4</code>.</p> 82 + <p>Do the same with the seaweed. The seaweed, being a smaller, 83 + flatter object, doesn't cast much of a shadow, so stop with 84 + 1 or 2 iterations of the gradient:</p> 85 + <p><img src="https://cdn.oppi.li/T3.png" alt="" /></p> 86 + <p>We're getting there!</p> 87 + <h4 id="highlights">Highlights</h4> 88 + <p>This step handles the details on the strongly illuminated 89 + portions of the object. Seaweed is a bit glossy, lighten the 90 + edges to make it seem shiny. The rice is not as shiny, but 91 + it does form an uneven surface. Add in some shadows to 92 + promote the idea of rice grains. Here is the finished 93 + result:</p> 94 + <p><img src="https://cdn.oppi.li/VE.png" alt="" /></p> 95 + <h3 id="finishing-touches">Finishing Touches</h3> 96 + <p>Some color correction and <code>a e s t h e t i c</code> Japanese text 97 + later, our piece is complete!</p> 98 + <p><img src="https://cdn.oppi.li/cn.png" alt="" /></p> 99 + <p>Hold on, why is it so tiny? Well, that's because our canvas 100 + was 100x100, head over to <code>Image &gt; Scale Image</code>, set 101 + <code>Quality &gt; Interpolation</code> to <code>None</code> and scale it up to 102 + 700x700, et voilà!</p> 103 + <p><img src="https://cdn.oppi.li/CH.png" alt="" /></p> 104 + </div></main></div></body></html>
+258
_site/posts/plain_text_journaling/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>plain text journaling</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">plain text journaling</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">plain text journaling</h1><div class="text-center space-x-2"><span>6 min</span><span>·</span><span>69 sentences</span><span>·</span><span>176.29 cm</span><span>·</span><span>18.06.2023</span></div><p>I cobbled together a journaling system with {neo,}vim, 3 + coreutils and <a href="http://www.fresse.org/dateutils">dateutils</a>. 4 + This system is loosely based on <a href="https://www.rydercarroll.com/">Ryder 5 + Caroll's</a> Bullet Journal 6 + method.</p> 7 + <p><a href="https://cdn.oppi.li/SpF.png"><img src="https://cdn.oppi.li/SpF.png" alt="" /></a></p> 8 + <h3 id="the-format">The format</h3> 9 + <p>The journal for a given year is a directory:</p> 10 + <pre><code class="language-bash">λ ls journal/ 11 + 2022/ 2023/ 12 + </code></pre> 13 + <p>In each directory are 12 files, one for each month of the 14 + year, numbered like so:</p> 15 + <pre><code class="language-bash">λ ls journal/2023/ 16 + 01 02 03 04 05 06 07 08 09 10 11 12 17 + </code></pre> 18 + <p>We can now begin writing stuff down:</p> 19 + <pre><code class="language-bash">λ vim journal/2023/1 20 + </code></pre> 21 + <p>Every month must start with a calendar of course, fill that 22 + in with:</p> 23 + <pre><code class="language-vim">:read !cal -m 24 + </code></pre> 25 + <p>Your entry for January might look like this:</p> 26 + <pre><code class="language-bash">λ cat journal/2023/01 27 + January 2023 28 + Mo Tu We Th Fr Sa Su 29 + 1 30 + 2 3 4 5 6 7 8 31 + 9 10 11 12 13 14 15 32 + 16 17 18 19 20 21 22 33 + 23 24 25 26 27 28 29 34 + 30 31 35 + </code></pre> 36 + <p>I prefer planning week by week, as opposed to creating a 37 + task-list every day, here's what I have for the first couple 38 + of weeks:</p> 39 + <pre><code> January 2023 40 + Mo Tu We Th Fr Sa Su 41 + 1 42 + 2 3 4 5 6 7 8 43 + 9 10 11 12 13 14 15 44 + 16 17 18 19 20 21 22 45 + 23 24 25 26 27 28 29 46 + 30 31 47 + 48 + 49 + week 1 50 + 51 + done apply leaves 52 + done dload boarding pass 53 + moved reply to dan 54 + 55 + 56 + week 2 57 + 58 + todo reply to dan 59 + todo pack bags 60 + done travel insurance 61 + todo weigh luggage 62 + </code></pre> 63 + <p>I start the week by writing a header and each item that week 64 + is placed on its own line. The items are prefixed with a 65 + <code>todo</code> or a <code>done</code> signifier.</p> 66 + <h3 id="form-over-function">Form over function</h3> 67 + <p>Right off the bat, the signifiers look very noisy, Even more 68 + so once we start introducing variety (I use &quot;event&quot;, &quot;note&quot; 69 + and &quot;moved&quot;):</p> 70 + <pre><code>week 1 71 + 72 + todo apply leaves 73 + done dload boarding pass 74 + todo reply to dan 75 + event fr trip 76 + note weight 68.6 77 + </code></pre> 78 + <p>We can clean this up with &quot;abbreviations&quot; (<code>:h abbreviations</code>):</p> 79 + <pre><code class="language-vim">:iabbrev todo · 80 + :iabbrev done × 81 + </code></pre> 82 + <p>Now, typing this:</p> 83 + <pre><code>todo apply leaves 84 + </code></pre> 85 + <p>Automatically inserts:</p> 86 + <pre><code>· apply leaves 87 + </code></pre> 88 + <p>You can use <code>x</code> and <code>o</code> as well, but <code>×</code> (U+00D7, 89 + MULTIPLICATION SIGN) and <code>·</code> (U+00B7, MIDDLE DOT) are more 90 + ... <em>gourmet</em>.</p> 91 + <p>The other signifiers I use are:</p> 92 + <ul> 93 + <li><code>-</code> for note 94 + </li> 95 + <li><code>o</code> for event 96 + </li> 97 + <li><code>&gt;</code> for moved. 98 + </li> 99 + </ul> 100 + <p>Nit #2 is the lack of order. We can employ vim to introduce 101 + grouping and sorting. Select the list of entries for this 102 + week:</p> 103 + <pre><code class="language-vim">vip &quot; line-wise select inner paragraph 104 + :'&lt;,'&gt;sort &quot; the markers '&lt; and '&gt; are automatically inserted, 105 + &quot; they mark the start and end of the selection 106 + </code></pre> 107 + <p>We end up with:</p> 108 + <pre><code>week 1 109 + 110 + · apply leaves 111 + · reply to dan 112 + × dload boarding pass 113 + </code></pre> 114 + <p>The lines are grouped by their signifiers, segregating todo 115 + items from completed items. Luckily, MIDDLE DOT is lesser 116 + than MULTIPLICATION SIGN, so todo items are placed at the 117 + top. The same goes for <code>o</code> and <code>x</code> symbols, either set of 118 + signifiers will result in the same sorting order.</p> 119 + <p>We can shorten this select-paragraph-invoke-sort dance by 120 + setting the <code>formatprg</code> variable:</p> 121 + <pre><code class="language-vim">:set formatprg=sort\ -V 122 + </code></pre> 123 + <p>Now, hitting <code>gqip</code> should automatically group and sort the 124 + items for the week under the cursor, moving todo items to 125 + the top. Finding signifier glyphs that suit your sorting 126 + preference is a fun exercise.</p> 127 + <h3 id="syntax-highlighting">Syntax highlighting</h3> 128 + <p>Adding color to items introduces another layer of visual 129 + distinction. In truth, I like to deck it out just because.</p> 130 + <p>First, create a few syntax groups:</p> 131 + <pre><code class="language-vim">:syntax match JournalAll /.*/ &quot; captures the entire buffer 132 + :syntax match JournalDone /^×.*/ &quot; lines containing 'done' items: × 133 + :syntax match JournalTodo /^·.*/ &quot; lines containing 'todo' items: · 134 + :syntax match JournalEvent /^o.*/ &quot; lines containing 'event' items: o 135 + :syntax match JournalNote /^- .*/ &quot; lines containing 'note' items: - 136 + :syntax match JournalMoved /^&gt;.*/ &quot; lines containing 'moved' items: &gt; 137 + </code></pre> 138 + <p>Add highlights to each group:</p> 139 + <pre><code class="language-vim">:highlight JournalAll ctermfg=12 &quot; bright black 140 + :highlight JournalDone ctermfg=12 &quot; bright black 141 + :highlight JournalEvent ctermfg=6 &quot; cyan 142 + :highlight JournalMoved ctermfg=5 &quot; magenta 143 + :highlight JournalNote ctermfg=3 &quot; yellow 144 + </code></pre> 145 + <p>In my terminal, this is rendered like so:</p> 146 + <p><a href="https://u.peppe.rs/Du6.png"><img src="https://u.peppe.rs/Du6.png" alt="" /></a></p> 147 + <h3 id="habit-tracking">Habit tracking</h3> 148 + <p>While this is not a part of my journaling system anymore, a 149 + few headers and an awk script is all it takes to track 150 + habits. My weekly entries would include a couple of habit 151 + headers like so:</p> 152 + <pre><code>week 1 -------------- 153 + 154 + × wake up on time 155 + × water the plants 156 + 157 + spend 7.5 7 10 158 + --------------------- 159 + 160 + 161 + week 2 -------------- 162 + 163 + · make the bed 164 + · go to bed 165 + 166 + spend 30 2.75 6 167 + --------------------- 168 + </code></pre> 169 + <p>Here, under the <code>spend</code> header in week 1, are a list of 170 + expenditures accumulated over the week. The monthly spend is 171 + calculated with this awk script:</p> 172 + <pre><code class="language-awk">BEGIN {spend=0;} 173 + /spend/ {for(i=1;i&lt;=$NF;i++) spend+=$i;} 174 + END { printf spend &quot;eur&quot;} 175 + </code></pre> 176 + <p>And invoked like so:</p> 177 + <pre><code>λ awk -f spend.awk journal/2023/01 178 + 63.25eur 179 + </code></pre> 180 + <h3 id="reflection">Reflection</h3> 181 + <p>Journaling is not just about planning what is to come, but 182 + also reflecting on what has passed. It would make sense to 183 + simultaneously look at the past few weeks' entries while 184 + making your current one. To open multiple months of entries 185 + at the same time:</p> 186 + <pre><code>λ vim -O journal/2023/0{1,2,3} 187 + </code></pre> 188 + <p>Opens 3 months, side-by-side, in vertical splits:</p> 189 + <pre><code>JANUARY ------------ │ FEBRUARY ----------- │ MARCH -------------- 190 + │ │ 191 + Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su 192 + 1 │ 1 2 3 4 5 │ 1 2 3 4 5 193 + 2 3 4 5 6 7 8 │ 6 7 8 9 10 11 12 │ 6 7 8 9 10 11 12 194 + 9 10 11 12 13 14 15 │ 13 14 15 16 17 18 19 │ 13 14 15 16 17 18 19 195 + 16 17 18 19 20 21 22 │ 20 21 22 23 24 25 26 │ 20 21 22 23 24 25 26 196 + 23 24 25 26 27 28 29 │ 27 28 │ 27 28 29 30 31 197 + 30 31 │ │ 198 + │ │ 199 + │ │ 200 + WEEK 1 ------------- │ WEEK 1 ------------- │ WEEK 1 ------------- 201 + │ │ 202 + &gt; latex setup │ &gt; forex │ - weight: 64 203 + × make the bed │ × clean shoes │ &gt; close sg-pr 204 + × 03: dentist │ × buy clothes │ × facewash 205 + × integrate tsg │ × draw │ × groceries 206 + │ │ 207 + │ │ 208 + WEEK 2 ------------- │ WEEK 2 ------------- │ WEEK 2 ------------- 209 + │ │ 210 + × latex setup │ - viral fever │ &gt; close sg-pr 211 + × send invoice │ × forex │ × plan meet 212 + × stack-graph pr │ × activate sim │ × sg storage 213 + │ × bitlbee │ 214 + </code></pre> 215 + <h3 id="reducing-friction">Reducing friction</h3> 216 + <p>Journaling already requires a solid amount of discipline and 217 + consistency. The added friction of typing <code>vim journal/$CURRENT_YEAR/$CURRENT_MONTH</code> each time is doing no 218 + favors.</p> 219 + <p>To open the current month based on system time:</p> 220 + <pre><code class="language-bash">λ vim $(date +&quot;%Y/%m&quot;) 221 + </code></pre> 222 + <p>To open all the months within a 2 month window of today, is 223 + a little trickier. The command we wish to generate is (if 224 + today is 2023/12):</p> 225 + <pre><code class="language-bash">λ vim -O 2023/10 2023/11 2023/12 2024/01 2024/02 226 + </code></pre> 227 + <p>And that is where <code>dateseq</code> from 228 + <a href="http://www.fresse.org/dateutils">dateutils</a> comes in handy, 229 + for example:</p> 230 + <pre><code class="language-bash">λ dateseq 2012-02-01 2012-03-01 231 + 2012-02-01 232 + 2012-02-02 233 + 2012-02-03 234 + ... 235 + 2012-02-28 236 + 2012-02-29 237 + 2012-03-01 238 + </code></pre> 239 + <p>This script opens all months within a 2 month window of 240 + today:</p> 241 + <pre><code class="language-bash">λ vim -O $( 242 + dateseq \ 243 + &quot;$(date --date &quot;2 months ago&quot; +%Y/%m)&quot; \ 244 + &quot;$(date --date &quot;2 months&quot; +%Y/%m)&quot; \ 245 + -i %Y/%m \ 246 + -f %Y/%m 247 + ) 248 + </code></pre> 249 + <h3 id="fin">Fin</h3> 250 + <p>You can find a sample vimrc file here: 251 + <a href="https://git.peppe.rs/cli/journal/tree">cli/journal</a>, along 252 + with a nix flake file to kick things off.</p> 253 + <p>Plain text journaling can be just as much fun as a pen and 254 + paper. Throw in some ASCII art for each month, use swankier 255 + signifiers, or louder syntax highlighting. Don't expect 256 + forgiveness from org-mode users though.</p> 257 + <p><a href="https://cdn.oppi.li/ZCK.png"><img src="https://cdn.oppi.li/ZCK.png" alt="" /></a></p> 258 + </div></main></div></body></html>
+140
_site/posts/programming_on_34_keys/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>programming on 34 keys</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">programming on 34 keys</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">programming on 34 keys</h1><div class="text-center space-x-2"><span>4 min</span><span>·</span><span>68 sentences</span><span>·</span><span>81.29 cm</span><span>·</span><span>28.08.2022</span></div><p>Minimizing your keyboard layout is a slippery slope. A few 3 + months ago, I built the 4 + <a href="https://github.com/icyphox/ferricy">Ferricy</a>, a 5 + 34-key-split-ortho-ergo keyboard. The Ferricy is a fork of 6 + the <a href="https://github.com/davidphilipbarr/Sweep/tree/main/Sweep%20Bling%20MX">Ferris Sweep MX 7 + Bling</a>.</p> 8 + <p><img src="https://cdn.oppi.li/otz.jpg" alt="The Ferricy, designed by icyphox" /></p> 9 + <p>My daily use consists of a bit of prose and a lot of 10 + program, my layout has evolved accordingly.</p> 11 + <h1 id="base-layer">Base Layer</h1> 12 + <p><img src="https://cdn.oppi.li/base.png" alt="Colemak with no mods" /></p> 13 + <p>The base layer contains alphabets, four symbols and four 14 + whitespace keys:</p> 15 + <ul> 16 + <li>Alphas: Stock Colemak, with no modifications whatsoever 17 + </li> 18 + <li>Symbols: <code>. , / ;</code> 19 + </li> 20 + <li>Whitespace: tab, space, enter, backspace (from left to 21 + right) 22 + </li> 23 + </ul> 24 + <h1 id="layers">Layers</h1> 25 + <p>Keyboard input is complex and it is impossible to skirt 26 + around it. You can either use a keyboard with enough keys to 27 + supply all possible inputs (a mechanical burden), or you can 28 + use firmware to supply all possible inputs (a cognitive 29 + burden). Layers are a cognitive burden.</p> 30 + <p>I use 3 layers, heavily inspired by 31 + <a href="https://github.com/manna-harbour/miryoku">Miryoku</a>, but 32 + tuned for programming. Excluding the base Colemak layer:</p> 33 + <ul> 34 + <li><code>NAV</code>: activated on holding <code>space</code> (left thumb) 35 + </li> 36 + <li><code>NUM</code>: activated on holding <code>tab</code> (left thumb) 37 + </li> 38 + <li><code>SYM</code>: activated on holding <code>enter</code> (right thumb) 39 + </li> 40 + </ul> 41 + <h2 id="the-nav-layer">The <code>NAV</code> Layer</h2> 42 + <p>As the name suggests, this layer is focused on navigation. 43 + Arrow keys and the likes.</p> 44 + <p><img src="https://cdn.oppi.li/nav.png" alt="NAV, on holding space" /></p> 45 + <p>Using Vim and Colemak means you lose out on HJKL navigation. 46 + However, on activating the <code>NAV</code> layer, the right home-row is 47 + converted into arrow keys. In essence, by holding space, I 48 + can navigate Vim with the home-row, or Firefox, or my PDF 49 + reader. I no longer need to look for software that allows 50 + Vim navigation keys, because it is baked into the firmware!</p> 51 + <p>My Vim motions are not limited to HJKL. In fact, my Vim 52 + motions are rarely HJKL. I tend to use <code>}</code> (next paragraph) 53 + and <code>)</code> (next sentence) more often. As a result, these have 54 + found their way into my <code>NAV</code> layer, over the likes of 55 + <code>PgDown</code> and <code>End</code>. Having brackets at my index and middle 56 + fingers is nice for programming too.</p> 57 + <h2 id="the-sym-layer">The <code>SYM</code> Layer</h2> 58 + <p><img src="https://cdn.oppi.li/sym.png" alt="SYM, on holding enter" /></p> 59 + <p>This layer contains all the symbols that you would find by 60 + hitting <code>Shift</code> and a key on the number row. Probably 61 + noteworthy to Vim users: the symbols are arranged in the 62 + form of a mirrored numpad for exactly one reason: to move 63 + <code>$</code> to the left of <code>^</code>. It has always annoyed me that <code>$</code> 64 + moves the cursor to the end of the line and <code>^</code> moves it to 65 + the beginning, but their position on a typical number row 66 + are reversed, 4 comes before 6.</p> 67 + <h2 id="the-num-layer">The <code>NUM</code> layer</h2> 68 + <p><img src="https://cdn.oppi.li/num.png" alt="NUM, on holding tab" /></p> 69 + <p>Another deviation from Miryoku, the numpad just feels <em>right</em> 70 + on my <em>right</em> hand.</p> 71 + <h1 id="zmk-combos">ZMK Combos</h1> 72 + <p>If you have been paying close attention, you might have 73 + noticed that <code>escape</code> didn't make it to any layer. <code>escape</code> 74 + is too crucial to put on a non-base layer, but at the same 75 + time, not as important to deserve a place on the base layer. 76 + That is where ZMK's combos come in. Combos let you tap any 77 + number of keys, and combine them to form a single key. I 78 + have combos set up for underscore, minus, escape and 79 + caps-word (more on caps-word later):</p> 80 + <p><img src="https://cdn.oppi.li/combos.png" alt="Combos are almost piano-like" /></p> 81 + <h1 id="home-row-mods">Home-row Mods</h1> 82 + <p>Inherited from Miryoku, I have home-row mods for activating 83 + <code>Super</code>, <code>Alt</code>, <code>Shift</code>, <code>Ctrl</code> and <code>Hyper</code> (<code>Ctrl + Shift + Alt + Super</code>). The idea is to send <code>T</code> on tap and <code>Ctrl</code> on 84 + hold. Home-row mods are fairly popular, so I'll not go into 85 + the details.</p> 86 + <p><img src="https://cdn.oppi.li/homerow.png" alt="Super, Alt, Shift, Ctrl, Hyper; on the left half, and mirrored on the right half" /></p> 87 + <p><code>Hyper</code> bridges the gap between firmware and software. You 88 + can never configure key combination that, opens Firefox, for 89 + example, through firmware alone. However, with the <code>Hyper</code> 90 + key, and some <code>sxhkd</code> magic, you can emulate that. Pressing 91 + <code>Hyper + F</code> on a keyboard is just two keys, but the key 92 + codes sent are <code>Ctrl + Shift + Alt + Super + F</code>. That key 93 + combination is not intercepted by any application as a 94 + shortcut, except for the following <code>sxhkd</code> stanza:</p> 95 + <pre><code class="language-bash">super + alt + shift + ctrl + f 96 + xdotool search &quot;Mozilla Firefox&quot; windowactivate 97 + </code></pre> 98 + <p>Alternatively, you can intercept unused <code>F</code> keys: <code>F13</code> 99 + through <code>F24</code>.</p> 100 + <p>Home-row mods are mirrored on each half because it would be 101 + impossible to hit <code>Ctrl + T</code> if not; they lie on the same 102 + key.</p> 103 + <h1 id="caps-word">Caps-word</h1> 104 + <p>Caps-word is a clever caps-lock, built into ZMK. Typing out 105 + constants such as <code>PORT</code> with home-row mods would look like 106 + this:</p> 107 + <ul> 108 + <li>hold <code>e</code> (shift) on left hand, and tap <code>p</code> on right hand 109 + </li> 110 + <li>hold <code>e</code> (shift) on left hand, and tap <code>o</code> on right hand 111 + </li> 112 + <li>hold <code>s</code> (shift) on right hand, and tap <code>r</code> on left hand 113 + </li> 114 + <li>hold <code>s</code> (shift) on right hand, and tap <code>t</code> on left hand 115 + </li> 116 + </ul> 117 + <p>This hold-alternate-hold dance gets tiring quickly. With 118 + caps-word, however:</p> 119 + <ul> 120 + <li>toggle <code>caps_word</code> 121 + </li> 122 + <li>type out <code>p</code>, <code>o</code>, <code>r</code>, <code>t</code> 123 + </li> 124 + <li>hit a <em>break</em> character (space, enter will do) 125 + </li> 126 + <li>continue 127 + </li> 128 + </ul> 129 + <p>Caps-word automatically disables capitalization upon 130 + encountering a breaking character, (which are space, enter 131 + or any modifier, by default) right in the firmware!</p> 132 + <h1 id="findings">Findings</h1> 133 + <p>34-keys has been reasonably comfortable to use, for both 134 + prose and program. My palms do not move across the desk at 135 + all, as I reach for keys. I mostly write Rust and Bash, and 136 + my layout has evolved to accomodate special characters from 137 + their grammars (angled brackets and hyphens, specifically). 138 + If you are on a similar journey, I would suggest focusing on 139 + accuracy and comfort over speed. Speed comes with time.</p> 140 + </div></main></div></body></html>
+148
_site/posts/rapid_refactoring_with_vim/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>rapid refactoring with vim</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">rapid refactoring with vim</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">rapid refactoring with vim</h1><div class="text-center space-x-2"><span>4 min</span><span>·</span><span>58 sentences</span><span>·</span><span>100.59 cm</span><span>·</span><span>31.03.2020</span></div><p>Last weekend, I was tasked with refactoring the 96 unit 3 + tests on 4 + <a href="https://github.com/ruma/ruma-events/pull/70">ruma-events</a> 5 + to use strictly typed json objects using <code>serde_json::json!</code> 6 + instead of raw strings. It was rather painless thanks to 7 + vim :)</p> 8 + <p>Here's a small sample of what had to be done (note the lines 9 + prefixed with the arrow):</p> 10 + <pre><code class="language-rust">→ use serde_json::{from_str}; 11 + 12 + #[test] 13 + fn deserialize() { 14 + assert_eq!( 15 + → from_str::&lt;Action&gt;(r#&quot;{&quot;set_tweak&quot;: &quot;highlight&quot;}&quot;#), 16 + Action::SetTweak(Tweak::Highlight { value: true }) 17 + ); 18 + } 19 + </code></pre> 20 + <p>had to be converted to:</p> 21 + <pre><code class="language-rust">→ use serde_json::{from_value}; 22 + 23 + #[test] 24 + fn deserialize() { 25 + assert_eq!( 26 + → from_value::&lt;Action&gt;(json!({&quot;set_tweak&quot;: &quot;highlight&quot;})), 27 + Action::SetTweak(Tweak::Highlight { value: true }) 28 + ); 29 + } 30 + </code></pre> 31 + <h3 id="the-arglist">The arglist</h3> 32 + <p>For the initial pass, I decided to handle imports, this was 33 + a simple find and replace operation, done to all the files 34 + containing tests. Luckily, modules (and therefore files) 35 + containing tests in Rust are annotated with the 36 + <code>#[cfg(test)]</code> attribute. I opened all such files:</p> 37 + <pre><code class="language-bash"># `grep -l pattern files` lists all the files 38 + # matching the pattern 39 + 40 + vim $(grep -l 'cfg\(test\)' ./**/*.rs) 41 + 42 + # expands to something like: 43 + vim push_rules.rs room/member.rs key/verification/lib.rs 44 + </code></pre> 45 + <p>Starting vim with more than one file at the shell prompt 46 + populates the arglist. Hit <code>:args</code> to see the list of 47 + files currently ready to edit. The square [brackets] 48 + indicate the current file. Navigate through the arglist 49 + with <code>:next</code> and <code>:prev</code>. I use tpope's vim-unimpaired 50 + <a href="https://github.com/tpope/vim-unimpaired">^un</a>, which adds <code>]a</code> and <code>[a</code>, mapped to <code>:next</code> and 51 + <code>:prev</code>.</p> 52 + <p>It also handles various other mappings, <code>]q</code> and <code>[q</code> to 53 + navigate the quickfix list for example</p> 54 + <p>All that's left to do is the find and replace, for which we 55 + will be using vim's <code>argdo</code>, applying a substitution to 56 + every file in the arglist:</p> 57 + <pre><code>:argdo s/from_str/from_value/g 58 + </code></pre> 59 + <h3 id="the-quickfix-list">The quickfix list</h3> 60 + <p>Next up, replacing <code>r#&quot; ... &quot;#</code> with <code>json!( ... )</code>. I 61 + couldn't search and replace that trivially, so I went with a 62 + macro call [^macro] instead, starting with the cursor on 63 + 'r', represented by the caret, in my attempt to breakdown 64 + the process:</p> 65 + <p>[^macro]: <code>:help recording</code></p> 66 + <pre><code>BUFFER: r#&quot; ... &quot;#; 67 + ^ 68 + 69 + ACTION: vllsjson!( 70 + 71 + BUFFER json!( ... &quot;#; 72 + ^ 73 + 74 + ACTION: &lt;esc&gt;$F# 75 + 76 + BUFFER: json!( ... &quot;#; 77 + ^ 78 + 79 + ACTION: vhs)&lt;esc&gt; 80 + 81 + BUFFER: json!( ... ); 82 + </code></pre> 83 + <p>Here's the recorded [^rec] macro in all its glory: 84 + <code>vllsjson!(&lt;esc&gt;$F#vhs)&lt;esc&gt;</code>.</p> 85 + <p>[^rec]: When I'm recording a macro, I prefer starting out by 86 + storing it in register <code>q</code>, and then copying it over to 87 + another register if it works as intended. I think of <code>qq</code> as 88 + 'quick record'.</p> 89 + <p>Great! So now we just go ahead, find every occurrence of 90 + <code>r#</code> and apply the macro right? Unfortunately, there were 91 + more than a few occurrences of raw strings that had to stay 92 + raw strings. Enter, the quickfix list.</p> 93 + <p>The idea behind the quickfix list is to jump from one 94 + position in a file to another (maybe in a different file), 95 + much like how the arglist lets you jump from one file to 96 + another.</p> 97 + <p>One of the easiest ways to populate this list with a bunch 98 + of positions is to use <code>vimgrep</code>:</p> 99 + <pre><code># basic usage 100 + :vimgrep pattern files 101 + 102 + # search for raw strings 103 + :vimgrep 'r#' ./**/*.rs 104 + </code></pre> 105 + <p>Like <code>:next</code> and <code>:prev</code>, you can navigate the quickfix list 106 + with <code>:cnext</code> and <code>:cprev</code>. Every time you move up or down 107 + the list, vim indicates your index:</p> 108 + <pre><code>(1 of 131): r#&quot;{&quot;set_tweak&quot;: &quot;highlight&quot;}&quot;#; 109 + </code></pre> 110 + <p>And just like <code>argdo</code>, you can <code>cdo</code> to apply commands to 111 + <em>every</em> match in the quickfix list:</p> 112 + <pre><code>:cdo norm! @q 113 + </code></pre> 114 + <p>But, I had to manually pick out matches, and it involved 115 + some button mashing.</p> 116 + <h3 id="external-filtering">External Filtering</h3> 117 + <p>Some code reviews later, I was asked to format all the json 118 + inside the <code>json!</code> macro. All you have to do is pass a 119 + visual selection through a pretty json printer. Select the 120 + range to be formatted in visual mode, and hit <code>:</code>, you will 121 + notice the command line displaying what seems to be 122 + gibberish:</p> 123 + <pre><code>:'&lt;,'&gt; 124 + </code></pre> 125 + <p><code>'&lt;</code> and <code>'&gt;</code> are <em>marks</em> [^mark-motions]. More 126 + specifically, they are marks that vim sets automatically 127 + every time you make a visual selection, denoting the start 128 + and end of the selection.</p> 129 + <p>[^mark-motions]: <code>:help mark-motions</code></p> 130 + <p>A range is one or more line specifiers separated by a <code>,</code>:</p> 131 + <pre><code>:1,7 lines 1 through 7 132 + :32 just line 32 133 + :. the current line 134 + :.,$ the current line to the last line 135 + :'a,'b mark 'a' to mark 'b' 136 + </code></pre> 137 + <p>Most <code>:</code> commands can be prefixed by ranges. <code>:help usr_10.txt</code> for more on that.</p> 138 + <p>Alright, lets pass json through <code>python -m json.tool</code>, a 139 + json formatter that accepts <code>stdin</code> (note the use of <code>!</code> to 140 + make use of an external program):</p> 141 + <pre><code>:'&lt;,'&gt;!python -m json.tool 142 + </code></pre> 143 + <p>Unfortunately that didn't quite work for me because the 144 + range included some non-json text as well, a mix of regex 145 + and macros helped fix that. I think you get the drift.</p> 146 + <p>Another fun filter I use from time to time is <code>:!sort</code>, to 147 + sort css attributes, or <code>:!uniq</code> to remove repeated imports.</p> 148 + </div></main></div></body></html>
+174
_site/posts/self-hosting_git/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>self-hosting git</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">self-hosting git</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">self-hosting git</h1><div class="text-center space-x-2"><span>4 min</span><span>·</span><span>68 sentences</span><span>·</span><span>112.28 cm</span><span>·</span><span>17.10.2020</span></div><p>Earlier this week, I began migrating my repositories from 3 + Github to <a href="https://git.zx2c4.com/cgit/about/">cgit</a>. If you care at 4 + all about big corporates turning open-source into a T-shirt 5 + farming service, this is the way to go.</p> 6 + <h3 id="offerings">Offerings</h3> 7 + <p>cgit is <em>very</em> bare bones. It is 8 + <a href="https://tools.ietf.org/html/rfc3875">cgi-based</a> web 9 + interface to git, and nothing more. You may browse 10 + repositories, view diffs, commit logs and even clone via 11 + http. If you are looking to replace Github with cgit, keep 12 + in mind that cgit does not handle issues or pull/merge 13 + requests. If people wish to contribute to your work, they 14 + would have to send you a patch via email.</p> 15 + <h3 id="setup">Setup</h3> 16 + <p>Installing cgit is fairly straightforward, if you would 17 + like to compile it from source:</p> 18 + <pre><code class="language-sh"># fetch 19 + git clone https://git.zx2c4.com &amp;&amp; cd cgit 20 + git submodule init 21 + git submodule update 22 + 23 + # install 24 + make NO_LUA=1 25 + sudo make install 26 + </code></pre> 27 + <p>This would drop the cgit cgi script (and the default css) 28 + into <code>/var/www/htdocs/cgit</code>. You may configure cgit by 29 + editing <code>/etc/cgitrc</code>. I specify the <code>NO_LUA</code> flag to 30 + compile without lua support, exclude that flag if you would 31 + like to extend cgit via lua scripts.</p> 32 + <h3 id="going-live">Going live</h3> 33 + <p>You might want to use, 34 + <a href="https://github.com/gnosek/fcgiwrap">fcgiwrap</a>, a 35 + <a href="http://www.nongnu.org/fastcgi">fastcgi</a> wrapper for <code>cgi</code> 36 + scripts,</p> 37 + <pre><code class="language-sh">sudo apt install fcgiwrap 38 + sudo systemctl start fcgiwrap.socket 39 + </code></pre> 40 + <p>Expose the cgit cgi script to the web via <code>nginx</code>:</p> 41 + <pre><code># nginx.conf 42 + server { 43 + listen 80; 44 + server_name git.example.com; 45 + 46 + # serve static files 47 + location ~* ^.+\.(css|png|ico)$ { 48 + root /var/www/htdocs/cgit; 49 + } 50 + 51 + location / { 52 + fastcgi_pass unix:/run/fcgiwrap.socket; 53 + fastcgi_param SCRIPT_FILENAME /var/www/htdocs/cgit/cgit.cgi; # the default location of the cgit cgi script 54 + fastcgi_param PATH_INFO $uri; 55 + fastcgi_param QUERY_STRING $args; 56 + } 57 + } 58 + </code></pre> 59 + <p>Point cgit to your git repositories:</p> 60 + <pre><code># /etc/cgitrc 61 + scan-path=/path/to/git/repos 62 + </code></pre> 63 + <p><em><strong>Note</strong></em>: <em><code>scan-path</code> works best if you stick it at the end of your 64 + <code>cgitrc</code></em>.</p> 65 + <p>You may now create remote repositories at 66 + <code>/path/to/git/repos</code>, via:</p> 67 + <pre><code>git init --bare 68 + </code></pre> 69 + <p>Add the remote to your local repository:</p> 70 + <pre><code>git remote set-url origin user@remote:/above/path 71 + git push origin master 72 + </code></pre> 73 + <h3 id="configuration">Configuration</h3> 74 + <p>cgit is fairly easy to configure, all configuration 75 + options can be found <a href="https://git.zx2c4.com/cgit/tree/cgitrc.5.txt">in the 76 + manual</a>, here 77 + are a couple of cool ones though:</p> 78 + <p><strong>enable-commit-graph</strong>: Generates a text based graphical 79 + representation of the commit history, similar to <code>git log --graph --oneline</code>.</p> 80 + <pre><code>| * | Add support for configuration file 81 + * | | simplify command parsing logic 82 + * | | Refactor parsers 83 + * | | Add basic tests 84 + * | | Merge remote-tracking branch 'origin/master' in... 85 + |\| | 86 + | * | add installation instructions for nix 87 + | * | switch to pancurses backendv0.2.2 88 + | * | bump to v0.2.2 89 + * | | Merge branch 'master' into feature/larger-names... 90 + |\| | 91 + | * | enable feature based compilation to support win... 92 + | * | remove dependency on rustc v1.45, bump to v0.2.... 93 + | * | Merge branch 'feature/windows' of https://git... 94 + | |\ \ 95 + | | * | add windows to github actions 96 + | | * | switch to crossterm backend 97 + | | * | Merge branch 'fix/duplicate-habits' 98 + | | |\ \ 99 + | | | * | move duplicate check to command parsing blo... 100 + </code></pre> 101 + <p><strong>section-from-path</strong>: This option paired with <code>scan-path</code> 102 + will automatically generate sections in your cgit index 103 + page, from the path to each repo. For example, the directory 104 + structure used to generate sections on <a href="https://git.peppe.rs">my cgit 105 + instance</a> looks like this:</p> 106 + <pre><code>├── cli 107 + │ ├── dijo 108 + │ ├── eva 109 + │ ├── pista 110 + │ ├── taizen 111 + │ └── xcursorlocate 112 + ├── config 113 + │ ├── dotfiles 114 + │ └── nixos 115 + ├── fonts 116 + │ ├── curie 117 + │ └── scientifica 118 + ├── languages 119 + │ └── lisk 120 + ├── libs 121 + │ ├── cutlass 122 + │ └── fondant 123 + ├── terminfo 124 + ├── university 125 + │ └── furby 126 + └── web 127 + └── isostatic 128 + </code></pre> 129 + <h3 id="ease-of-use">Ease of use</h3> 130 + <p>As I mentioned before, <code>cgit</code> is simply a view into your git 131 + repositories, you will have to manually create new 132 + repositories by entering your remote and using <code>git init --bare</code>. Here are a couple of scripts I wrote to perform 133 + actions on remotes, think of it as a smaller version of 134 + Github's <code>gh</code> program.</p> 135 + <p>You may save these scripts as <code>git-script-name</code> and drop 136 + them in your <code>$PATH</code>, and git will automatically add an 137 + alias called <code>script-name</code>, callable via:</p> 138 + <pre><code>git script-name 139 + </code></pre> 140 + <h4 id="git-new-repo">git-new-repo</h4> 141 + <p>Creates a new repository on your remote, 142 + the first arg may be a path (section/repo-name) or just the 143 + repo name:</p> 144 + <pre><code>#! /usr/bin/env bash 145 + # 146 + # usage: 147 + # git new-repo section/repo-name 148 + # 149 + # example: 150 + # git new-repo fonts/scientifica 151 + # creates: user@remote:fonts/scientifica 152 + 153 + if [ $# -eq 0 ]; then 154 + echo &quot;requires an arg&quot; 155 + exit 1 156 + fi 157 + 158 + ssh user@remote git init --bare &quot;$1&quot;; 159 + </code></pre> 160 + <h4 id="git-set-desc">git-set-desc</h4> 161 + <p>To set a one line repository 162 + description. It simply copies the local <code>.git/description</code>, 163 + into <code>remote/description</code>. <code>cgit</code> displays the contents of 164 + this file on the index page:</p> 165 + <pre><code>#! /usr/bin/env bash 166 + # 167 + # usage: 168 + # enter repo description into .git/description and run: 169 + # git set-desc 170 + 171 + remote=$(git remote get-url --push origin) 172 + scp .git/description &quot;$remote/description&quot; 173 + </code></pre> 174 + </div></main></div></body></html>
+71
_site/posts/snip_snap/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>snip snap</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">snip snap</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">snip snap</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>10 sentences</span><span>·</span><span>51.82 cm</span><span>·</span><span>29.05.2024</span></div><p>I regularly switch between exactly two things while working, 3 + a &quot;current&quot; and an &quot;alternate&quot; item; a lot of tools I use 4 + seem to support this flow.</p> 5 + <h4 id="git">git</h4> 6 + <p>Pass <code>-</code> to <code>git-checkout</code> to switch to the previously 7 + active branch:</p> 8 + <pre><code class="language-bash">$ git branch 9 + * foo 10 + bar 11 + 12 + $ git checkout bar 13 + $ git branch 14 + foo 15 + * bar 16 + 17 + $ git checkout - 18 + $ git branch 19 + * foo 20 + bar 21 + </code></pre> 22 + <h4 id="bash---cd">bash - cd</h4> 23 + <p>This may not be exclusive to <code>bash</code>:</p> 24 + <pre><code class="language-bash">~/foo $ cd ~/bar 25 + ~/bar $ cd - 26 + ~/foo $ 27 + </code></pre> 28 + <p>This is especially handy in combination with my <a href="../curing_a_case_of_git-UX/">git-worktree 29 + flow</a>:</p> 30 + <pre><code class="language-bash">~/main-branch $ gwj feature 31 + ~/feat-branch $ cd - 32 + ~/main-branch $ 33 + </code></pre> 34 + <h4 id="bash---jobs">bash - jobs</h4> 35 + <p>I often suspend multiple <code>vim</code> sessions with <code>Ctrl-Z</code>:</p> 36 + <pre><code class="language-bash">$ jobs 37 + [1]+ Stopped vim transpiler/src/transform.rs 38 + [2]- Stopped git commit --verbose 39 + </code></pre> 40 + <p>In the above example: I suspended <code>vim</code> when working on 41 + <code>transform.rs</code>, and then began working on a commit by 42 + running <code>git commit</code> without a message flag (lets you craft 43 + a message in <code>$EDITOR</code>). To bring the current job to the 44 + foreground, you can use <code>fg</code>:</p> 45 + <pre><code class="language-bash">$ fg 46 + </code></pre> 47 + <p>With a job identifier:</p> 48 + <pre><code class="language-bash">$ fg %2 # resumes interactive git commit 49 + </code></pre> 50 + <p>Or switch to &quot;last&quot; job, or the second-most-recently-resumed 51 + job:</p> 52 + <pre><code class="language-bash">$ fg %- 53 + $ %- # shorthand 54 + 55 + </code></pre> 56 + <h4 id="vim">vim</h4> 57 + <p>Switch to the last active buffer with <code>Ctrl+^</code>. In 58 + command-mode, <code>#</code> refers to the last active buffer, you can 59 + use this as an argument to a few commands:</p> 60 + <pre><code class="language-vimscript">:b# &quot; switch to alternate buffer (same as Ctrl+^) 61 + :vsp# &quot; create a vertical split with the alternate buffer 62 + :read# &quot; read contents of alternate buffer into current buffer 63 + :!wc # &quot; pass file name of alternate buffer to the command `wc` 64 + </code></pre> 65 + <p>See <code>:help c_#</code> for more.</p> 66 + <h4 id="tmux">tmux</h4> 67 + <p>Switch to the last active tmux session with 68 + <code>&lt;prefix&gt;+shift+L</code>.</p> 69 + <h4 id="qutebrowser">qutebrowser</h4> 70 + <p>Switch to the last active tab with <code>g$</code>.</p> 71 + </div></main></div></body></html>
+47
_site/posts/static_sites_with_bash/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>static sites with bash</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">static sites with bash</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">static sites with bash</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>24 sentences</span><span>·</span><span>26.93 cm</span><span>·</span><span>22.11.2019</span></div><p>After going through a bunch of static site generators 3 + (<a href="https://blog.getpelican.com/">pelican</a>, 4 + <a href="https://gohugo.io">hugo</a>, 5 + <a href="https://github.com/icyphox/vite">vite</a>), I decided to roll 6 + my own. If you are more of the 'show me the code' kinda guy, 7 + <a href="https://github.com/nerdypepper/site">here</a> you go.</p> 8 + <h3 id="text-formatting">Text formatting</h3> 9 + <p>I chose to write in markdown, and convert 10 + to html with <a href="https://kristaps.bsd.lv/lowdown/">lowdown</a>.</p> 11 + <h3 id="directory-structure">Directory structure</h3> 12 + <p>I host my site on GitHub pages, so 13 + <code>docs/</code> has to be the entry point. Markdown formatted posts 14 + go into <code>posts/</code>, get converted into html, and end up in 15 + <code>docs/index.html</code>, something like this:</p> 16 + <pre><code class="language-bash">posts=$(ls -t ./posts) # chronological order! 17 + for f in $posts; do 18 + file=&quot;./posts/&quot;$f # `ls` mangled our file paths 19 + echo &quot;generating post $file&quot; 20 + 21 + html=$(lowdown &quot;$file&quot;) 22 + echo -e &quot;html&quot; &gt;&gt; docs/index.html 23 + done 24 + </code></pre> 25 + <h3 id="assets">Assets</h3> 26 + <p>Most static site generators recommend dropping image 27 + assets into the site source itself. That does have it's 28 + merits, but I prefer hosting images separately:</p> 29 + <pre><code class="language-bash"># strip file extension 30 + ext=&quot;${1##*.}&quot; 31 + 32 + # generate a random file name 33 + id=$( cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 2 | head -n 1 ) 34 + id=&quot;$id.$ext&quot; 35 + 36 + # copy to my file host 37 + scp -P 443 &quot;$1&quot; emerald:files/&quot;$id&quot; 38 + echo &quot;https://cdn.oppi.li/$id&quot; 39 + </code></pre> 40 + <h3 id="templating">Templating</h3> 41 + <p><a href="https://github.com/NerdyPepper/site/blob/master/generate.sh"><code>generate.sh</code></a> 42 + brings the above bits and pieces together (with some extra 43 + cruft to avoid javascript). It uses <code>sed</code> to produce nice 44 + titles from the file names (removes underscores, 45 + title-case), and <code>date(1)</code> to add the date to each post 46 + listing!</p> 47 + </div></main></div></body></html>
+136
_site/posts/tales_from_mainframe_modernization/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>tales from mainframe modernization</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">tales from mainframe modernization</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">tales from mainframe modernization</h1><div class="text-center space-x-2"><span>2 min</span><span>·</span><span>74 sentences</span><span>·</span><span>84.33 cm</span><span>·</span><span>21.05.2025</span></div><p>At my last workplace, I wrote transpilers (or just 3 + <a href="https://people.csail.mit.edu/rachit/post/transpiler/">compilers</a> 4 + if you prefer) from mainframe languages (COBOL, JCL, BASIC etc.) to 5 + Java (in Rust!).</p> 6 + <p>Legacy code is full of surprises. In the roughly 200k lines 7 + of COBOL that I had the (dis)pleasure of working with, I saw 8 + some wonderful hacks to get around the limitations of the 9 + system. Mainframes are also chock full of history.</p> 10 + <h3 id="base-10-numerics">Base-10 numerics</h3> 11 + <p>This is the first thing that stood out to me when I looked 12 + at COBOL code, a data-definition (the phrase for &quot;variable&quot;) 13 + in COBOL is declared like so:</p> 14 + <pre><code> ,-- name 15 + | ,- type 16 + __|___ __|_ 17 + 01 HEIGHT PIC 9(3). 18 + -- --- 19 + | | 20 + | `- picture clause (keyword) 21 + `- level number 22 + </code></pre> 23 + <p>That statement declares a variable called <code>HEIGHT</code> with type 24 + <code>9(3)</code>, which is shorthand for <code>999</code>, which indicates 25 + &quot;3-digit number&quot;. The possible values for this variable are 26 + <code>0</code> to <code>999</code>!</p> 27 + <h3 id="internationalisation">Internationalisation</h3> 28 + <p>Below is another data-definition in COBOL, declaring 3 29 + variables:</p> 30 + <pre><code class="language-cobol">01 FOO-PERSON. 31 + 05 FOO-NAME PIC X(5). 32 + 05 FOO-HEIGHT PIC 9(3). 33 + </code></pre> 34 + <p>What that means is:</p> 35 + <ul> 36 + <li><code>FOO-PERSON</code>: a &quot;group&quot; variable consisting of two other 37 + variables 38 + </li> 39 + <li><code>FOO-NAME</code>: an alphanumeric type with 5 characters 40 + </li> 41 + <li><code>FOO-HEIGHT</code>: a numeric type with 3 digits (remember, base 10 42 + and not base 2) 43 + </li> 44 + </ul> 45 + <p>COBOL has an interesting construct called &quot;REDEFINES&quot;:</p> 46 + <pre><code class="language-cobol">01 FOO-PERSON. 47 + 05 FOO-NAME PIC X(5). 48 + 05 FOO-HEIGHT PIC 9(3). 49 + 50 + 01 FOO-PERSONNE REDEFINES FOO-PERSON. 51 + 05 FOO-NOM PIC X(5). 52 + 05 FOO-TAILLE PIC 9(3). 53 + </code></pre> 54 + <p><code>FOO-PERSON</code> and <code>FOO-PERSONNE</code> refer to the same region of 55 + memory.</p> 56 + <p>I helped modernise a codebase that had clearly been worked 57 + on by a Spanish consultancy at some point, and they had 58 + decided to redefine all data definitions in Spanish.</p> 59 + <h3 id="string-parsing">String parsing</h3> 60 + <p>Here's another fun one:</p> 61 + <pre><code class="language-cobol"> 01 FOO-PERSON. 62 + 05 FOO-NAME PIC X(5). 63 + 05 FOO-HEIGHT PIC 9(3). 64 + . 65 + . 66 + . 67 + 68 + MOVE &quot;PETER&quot; TO FOO-NAME. 69 + MOVE 175 TO FOO-HEIGHT. 70 + 71 + *&gt; display the entire memory region 72 + DISPLAY FOO-PERSON. 73 + *&gt; PETER175 74 + 75 + *&gt; subscripting the first 7 bytes... 76 + DISPLAY FOO-PERSON (1:7) 77 + *&gt; PETER17 78 + </code></pre> 79 + <p>So data-definitions simply describe names for regions. Which 80 + enables a clever way to parse strings:</p> 81 + <pre><code class="language-cobol"> 01 DATE. 82 + 05 DD PIC 9(2). 83 + 05 FILLER PIC X. 84 + 05 MMM PIC A(3). 85 + 05 FILLER PIC X. 86 + 05 YYYY PIC 9(4). 87 + 88 + . 89 + . 90 + . 91 + 92 + MOVE &quot;03 MAR 2025&quot; TO DATE. 93 + DISPLAY &quot;DAY: &quot; DD. *&gt; DAY: 03 94 + DISPLAY &quot;MONTH: &quot; MMM. *&gt; MONTH: MAR 95 + DISPLAY &quot;YEAR: &quot; YYYY. *&gt; YEAR: 2025 96 + 97 + *&gt; also works: 98 + MOVE &quot;03-MAR-2025&quot; TO DATE. 99 + </code></pre> 100 + <h3 id="early-exit">Early exit</h3> 101 + <p>I'd see this peppered around in a few places; which I later 102 + realized was a way to trigger an abnormal end to a batch job 103 + (possibly triggering an error handling routine in the outer 104 + job control system):</p> 105 + <pre><code class="language-cobol"> 01 CONSTANT-ZERO S9(9)V9 VALUE 0. 106 + 01 ABEND S9(9)V9. 107 + 108 + . 109 + . 110 + . 111 + 112 + COMPUTE ABEND = CONSTANT-ZERO / CONSTANT-ZERO. 113 + </code></pre> 114 + <h3 id="all-the-numbers">All the numbers</h3> 115 + <p>I have yet to find an explanation for this one, but I once 116 + found a file with just the first 800 natural numbers defined 117 + as string constants:</p> 118 + <pre><code class="language-cobol"> 01 TC0001 X(5) &quot;00001&quot;. 119 + 01 TC0002 X(5) &quot;00002&quot;. 120 + 01 TC0003 X(5) &quot;00003&quot;. 121 + . 122 + . 123 + *&gt; .... 800 lines later .... 124 + . 125 + . 126 + 01 TC0800 X(5) &quot;00800&quot;. 127 + </code></pre> 128 + <p>The file was definitely not generated, and I can't imagine 129 + text editors on the mainframe were all that advanced either.</p> 130 + <h3 id="dd---disk-destroyer"><code>dd</code> - disk destroyer</h3> 131 + <p>The <code>DD</code> statement in the JCL subsystem stands for &quot;data 132 + definition&quot;, which is largely used to describe files and IO 133 + streams used by a batch job. The <code>dd</code> command [^dd] on UNIX is 134 + named after this statement!</p> 135 + <p>[^dd]: <a href="https://en.wikipedia.org/wiki/Dd_%28Unix%29#History">Wikipedia - dd (Unix)</a></p> 136 + </div></main></div></body></html>
+37
_site/posts/termux_tandem/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>termux tandem</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">termux tandem</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">termux tandem</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>36 sentences</span><span>·</span><span>24.39 cm</span><span>·</span><span>07.03.2020</span></div><p>I learnt about <code>termux</code> from a friend on IRC recently. 3 + It looked super gimmicky to me at first, but it eventually 4 + proved to be useful. Here's what I use it for:</p> 5 + <h3 id="rsync">rsync</h3> 6 + <p>Ever since I degoogled my android device, syncing files 7 + between my phone and my PC has always been a pain. I'm 8 + looking at you MTP. But, with <code>termux</code> and <code>sshd</code> all set up, 9 + it's as simple as:</p> 10 + <pre><code>$ arp 11 + Address HWtype HWad ... 12 + 192.168.43.187 ether d0:0 ... 13 + 14 + $ rsync -avz 192.168.43.187:~/frogs ~/pics/frogs 15 + </code></pre> 16 + <h3 id="ssh-tmux">ssh &amp; tmux</h3> 17 + <p>My phone doubles as a secondary view into my main machine 18 + with <code>ssh</code> and <code>tmux</code>. When I am away from my PC (read: 19 + sitting across the room), I check build status and IRC 20 + messages by <code>ssh</code>ing into a tmux session running the said 21 + build or weechat.</p> 22 + <h3 id="file-uploads">file uploads</h3> 23 + <p>Not being able to access my (ssh-only) file host was 24 + crippling. With a <code>bash</code> instance on my phone, I just copied 25 + over my ssh keys, and popped in a file upload script (a 26 + glorified <code>scp</code>). Now I just have to figure out a way to 27 + clean up these file names ...</p> 28 + <pre><code>~/storage/pictures/ $ ls 29 + 02muf5g7b2i41.jpg 7alt3cwg77841.jpg cl4bsrge7id11.png 30 + mtZabXG.jpg p8d5c584f2841.jpg vjUxGjq.jpg 31 + </code></pre> 32 + <h3 id="cmus">cmus</h3> 33 + <p>Alright, I don't really listen to music via <code>cmus</code>, but I 34 + did use it a couple times when my default music player was 35 + acting up. <code>cmus</code> is a viable option:</p> 36 + <p><a href="https://cdn.oppi.li/CP.jpg"><img src="https://cdn.oppi.li/CP.jpg" alt="" /></a></p> 37 + </div></main></div></body></html>
+24
_site/posts/turing_complete_type_systems/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>turing complete type systems</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/posts">posts</a></span><span> :: <a href="#">turing complete type systems</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">turing complete type systems</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>13 sentences</span><span>·</span><span>12.19 cm</span><span>·</span><span>17.06.2020</span></div><p>Rust's type system is Turing complete:</p> 3 + <ul> 4 + <li><a href="https://github.com/doctorn/trait-eval/">FizzBuzz with Rust Traits</a> 5 + </li> 6 + <li><a href="https://github.com/Ashymad/fortraith">A Forth implementation with Rust Traits</a> 7 + </li> 8 + </ul> 9 + <p>It is impossible to determine if a program written in a 10 + generally Turing complete system will ever stop. That is, it 11 + is impossible to write a program <code>f</code> that determines if a 12 + program <code>g</code>, where <code>g</code> is written in a Turing complete 13 + programming language, will ever halt. The <a href="https://en.wikipedia.org/wiki/Halting_problem">Halting 14 + Problem</a> is 15 + in fact, an <a href="https://en.wikipedia.org/wiki/Undecidable_problem">undecidable 16 + problem</a>.</p> 17 + <p><em>How is any of this relevant?</em></p> 18 + <p>Rust performs compile-time type inference. The type checker, 19 + in turn, compiles and infers types, I would describe it as a 20 + compiler inside a compiler. It is possible that <code>rustc</code> may 21 + never finish compiling your Rust program! I lied, <code>rustc</code> 22 + stops after a while, after hitting the recursion limit.</p> 23 + <p>I understand that this post lacks content.</p> 24 + </div></main></div></body></html>
+1
_site/style.css
··· 1 + html{font-family:serif;line-height:1.2}h1,h2,h3,h4{margin-top:0;line-height:1.2;font-weight:bold}h1{font-size:1.8rem;margin-top:3rem}h2{font-size:1.5rem;margin-top:2.5rem}h3{font-size:1.25rem;margin-top:2rem}h4{font-size:1.1rem;margin-top:1.5rem}p{margin-top:1rem}ul{margin-top:1rem;padding-left:1.5rem;list-style-type:disc}ol{margin-top:1rem;padding-left:1.5rem;list-style-type:decimal}li{margin-top:.25rem}blockquote{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}pre{margin-top:1.5rem;margin-bottom:1.5rem;padding:.5rem;border:1px solid;overflow-x:auto}img{border:1px solid}a{color:revert;text-decoration-line:underline}hr{margin-top:2rem;margin-bottom:2rem}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mt-4{margin-top:calc(var(--spacing)*4)}.flex{display:flex}.max-w-xl{max-width:var(--container-xl)}.grow{flex-grow:1}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-right{text-align:right}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}html{font-family:serif;line-height:1.2}h1,h2,h3,h4{margin-top:0;line-height:1.2;font-weight:bold}h1{font-size:1.8rem;margin-top:3rem}h2{font-size:1.5rem;margin-top:2.5rem}h3{font-size:1.25rem;margin-top:2rem}h4{font-size:1.1rem;margin-top:1.5rem}p{margin-top:1rem}ul{margin-top:1rem;padding-left:1.5rem;list-style-type:disc}ol{margin-top:1rem;padding-left:1.5rem;list-style-type:decimal}li{margin-top:.25rem}blockquote{margin-top:1.5rem;margin-bottom:1.5rem;padding-left:1rem}pre{margin-top:1.5rem;margin-bottom:1.5rem;padding:.5rem;border:1px solid;overflow-x:auto}img{border:1px solid}a{color:revert;text-decoration-line:underline}hr{margin-top:2rem;margin-bottom:2rem}@media (prefers-color-scheme:dark){html{background-color:#000000;color:#d0d0d0}a{color:#7eb3f8}a:visited{color:#c084fc}html{background-color:#000000;color:#d0d0d0}a{color:#7eb3f8}a:visited{color:#c084fc}}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mt-2{margin-top:calc(var(--spacing)*2)}.mt-4{margin-top:calc(var(--spacing)*4)}.grid{display:grid}.w-fit{width:fit-content}.max-w-xl{max-width:var(--container-xl)}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-right{text-align:right}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-x-reverse:0}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mb-12{margin-bottom:calc(var(--spacing)*12)}.max-w-xl{max-width:var(--container-xl)}:where(.space-x-2>:not(:last-child)){--tw-space-x-reverse:0;margin-inline-start:calc(calc(var(--spacing)*2)*var(--tw-space-x-reverse));margin-inline-end:calc(calc(var(--spacing)*2)*calc(1 - var(--tw-space-x-reverse)))}.px-2{padding-inline:calc(var(--spacing)*2)}.text-center{text-align:center}.text-pretty{text-wrap:pretty}}@property --tw-space-x-reverse{syntax:"*";inherits:false;initial-value:0}@layer properties,theme,base,components,utilities;@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--font-mono:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--spacing:.25rem;--container-xl:36rem;--text-xs:.75rem;--text-xs--line-height:calc(1/.75);--text-lg:1.125rem;--text-lg--line-height:calc(1.75/1.125);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif,system-ui,sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.mx-auto{margin-inline:auto}.mt-4{margin-top:calc(var(--spacing)*4)}.mt-10{margin-top:calc(var(--spacing)*10)}.mt-12{margin-top:calc(var(--spacing)*12)}.flex{display:flex}.w-48{width:calc(var(--spacing)*48)}.max-w-xl{max-width:var(--container-xl)}.aspect-1\/1{aspect-ratio:1}.grow{flex-grow:1}.flex-col{flex-direction:column}.flex-row{flex-direction:row}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-12{gap:calc(var(--spacing)*12)}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing)*4)*var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing)*4)*calc(1 - var(--tw-space-y-reverse)))}.p-2{padding:calc(var(--spacing)*2)}.px-2{padding-inline:calc(var(--spacing)*2)}.text-justify{text-align:justify}.text-right{text-align:right}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}
+71
_site/weeklies/2026-16/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>2026-16</title><link rel="stylesheet" href="/style.css?v=b6dddbe3" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="/weeklies">weeklies</a></span><span> :: <a href="#">2026-16</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto text-pretty mb-12"><h1 class="text-center">2026-16</h1><div class="text-center space-x-2"><span>1 min</span><span>·</span><span>65 sentences</span><span>·</span><span>43.18 cm</span><span>·</span><span>19.04.2026</span></div><h2 id="fp-launchpad-at-iit-madras">FP Launchpad at IIT Madras</h2> 3 + <p>I had the pleasure of attending the inaugration of <a href="https://fplaunchpad.org/">FP 4 + Launchpad</a>, courtesy of <a href="https://kcsrk.info/">KC 5 + Sivaramakrishnan</a> and <a href="https://anil.recoil.org">Anil 6 + Madhavapeddy</a>. It was a packed 7 + schedule with back-to-back presentations. My personal 8 + favourites:</p> 9 + <ul> 10 + <li><a href="https://fplaunchpad.org/2026/03/30/fp-launchpad-kickoff.html#raghavan">Towards verifiable governance with LLMs and 11 + Lean</a> 12 + </li> 13 + <li><a href="https://fplaunchpad.org/2026/03/30/fp-launchpad-kickoff.html#krishnamurthi">A Programming Language for Lightweight 14 + Diagramming</a> 15 + </li> 16 + </ul> 17 + <p>It was truly rousing to see FP grab hold in Indian 18 + universities. I briefly attempted to run a series of <a href="https://github.com/DSCRV/lisk">Scheme 19 + &amp; Haskell</a> tutorials for my 20 + juniors at university several years ago!</p> 21 + <h2 id="renewed-interest-in-ocaml">Renewed interest in OCaml</h2> 22 + <p>After attending FP Launchpad, my interest in OCaml was at an 23 + all time high. So I've rewritten <a href="https://tangled.org/oppi.li/site/blob/dbc61fb36b60a574dd8e99af7caf8abfb2be2638/generate.sh">the ugly bash 24 + program</a> 25 + that was used to generate this website <a href="https://tangled.org/oppi.li/site">in 26 + OCaml</a>, using the 27 + wonderful <a href="https://github.com/samoht/tw">tw</a> library by 28 + <a href="https://gazagnaire.org/">Thomas Gazagnaire</a>. I've now added 29 + a section for <a href="/weeklies">weeklies</a>, to get myself to write 30 + more often.</p> 31 + <p>I've also been poking at 32 + <a href="https://tangled.org/patrick.sirref.org/bruit">bruit</a> to 33 + <a href="https://tangled.org/oppi.li/bruit/commit/87bdc3254ee028007805e6b7135fa0da83287fb9">have it work on the 34 + web</a>, 35 + so I can eventually show-off my OCaml implementation of 36 + <a href="https://wryl.tech/projects/modal.html">Modal</a> on a <a href="https://docs.tangled.org/hosting-websites-on-tangled.html#hosting-websites-on-tangled">Tangled 37 + site</a>.</p> 38 + <p>I've started familiarizing myself with OCaml's 39 + <a href="https://github.com/ocaml-ppx/ppxlib">metaprogramming 40 + ecosystem</a> to emit 41 + <a href="https://github.com/sidprasad/spytial">spytial diagrams</a> 42 + from OCaml data structures. It is ever so slightly different 43 + from 44 + <a href="https://docs.rs/proc-macro2/latest/proc_macro2/">proc_macro2</a> 45 + in that it operates on a full 46 + <a href="https://ocaml.org/manual/5.4/api/compilerlibref/Parsetree.html">Parsetree</a> 47 + and not a plain 48 + <a href="https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html">TokenStream</a>.</p> 49 + <h2 id="building-a-web-of-trust">Building a web-of-trust</h2> 50 + <p>I've been taking a few (cautious) steps towards implementing 51 + <a href="https://github.com/mitchellh/vouch/">vouching</a> on Tangled. 52 + More on that next week!</p> 53 + <h2 id="improving-my-music-setup">Improving my music setup</h2> 54 + <p><a href="https://tangled.org/devins.page/tinysub">tinysub</a>, a 55 + subsonic-compatible web player popped up on my Tangled feed, 56 + and I instantly took to setting it up on my homeserver. 57 + Using <a href="https://github.com/Y2Z/monolith">monolith</a>, I was 58 + able to build it down to a single 100kB HTML file, assets 59 + included.</p> 60 + <p>I then rigged up my navidrome server to scrobble listens 61 + directly to my 62 + <a href="https://atproto.com/guides/self-hosting#pds">PDS</a> using the 63 + <a href="https://tangled.org/oppi.li/rocksky/commit/47babb790cd34d70c7a3638bf8d145013cfef7fd#flake.nix-N42">rocksky 64 + CLI</a>. 65 + My scrobbles are now aggregated by 66 + <a href="https://rocksky.app">rocksky.app</a> and 67 + <a href="https://teal.fm">teal.fm</a>.</p> 68 + <h2 id="travel">Travel</h2> 69 + <p>I am now back in London! I've spent the weekend trying to 70 + revive my houseplants... with little luck.</p> 71 + </div></main></div></body></html>
+2
_site/weeklies/index.html
··· 1 + <!DOCTYPE html> 2 + <html lang="en"><head><meta charset="utf-8" /><title>weeklies</title><link rel="stylesheet" href="/style.css?v=1598ce9b" /><style>html { font-size-adjust: ex-height 0.53; }</style></head><body><div><nav class="max-w-xl mx-auto"><a href="/">home</a><span> :: <a href="#">weeklies</a></span></nav><main class="px-2"><div class="max-w-xl mx-auto"><h1 class="text-center">weeklies</h1><p class="text-center">1 of 1320 weeks documented</p><div class="grid grid-cols-3 mt-4"><div><h2>2026</h2><div class="grid grid-cols-4 mt-2 tabular-nums w-fit" style="column-gap: 1ch"><span class="text-right">1</span><span class="text-right">2</span><span class="text-right">3</span><span class="text-right">4</span><span class="text-right">5</span><span class="text-right">6</span><span class="text-right">7</span><span class="text-right">8</span><span class="text-right">9</span><span class="text-right">10</span><span class="text-right">11</span><span class="text-right">12</span><span class="text-right">13</span><span class="text-right">14</span><span class="text-right">15</span><a class="text-right" href="2026-16">16</a><span class="text-right">17</span><span class="text-right">18</span><span class="text-right">19</span><span class="text-right">20</span><span class="text-right">21</span><span class="text-right">22</span><span class="text-right">23</span><span class="text-right">24</span><span class="text-right">25</span><span class="text-right">26</span><span class="text-right">27</span><span class="text-right">28</span><span class="text-right">29</span><span class="text-right">30</span><span class="text-right">31</span><span class="text-right">32</span><span class="text-right">33</span><span class="text-right">34</span><span class="text-right">35</span><span class="text-right">36</span><span class="text-right">37</span><span class="text-right">38</span><span class="text-right">39</span><span class="text-right">40</span><span class="text-right">41</span><span class="text-right">42</span><span class="text-right">43</span><span class="text-right">44</span><span class="text-right">45</span><span class="text-right">46</span><span class="text-right">47</span><span class="text-right">48</span><span class="text-right">49</span><span class="text-right">50</span><span class="text-right">51</span><span class="text-right">52</span></div></div></div></div></main></div></body></html>
-49
art.esh
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Art · oppi.li"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Art · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <a href="/" class="post-end-link">Home</a> 19 - <span>/</span> 20 - <a href="/posts" class="post-end-link">Art</a> 21 - <h1>Art</h1> 22 - <div class="separator"></div> 23 - <div id="photos"> 24 - <% 25 - source ./scripts.sh 26 - for f in `ls -t ./art`; do 27 - cat << XXX 28 - <a href="/art/$f"> 29 - <div class="photo-container"> 30 - <img src="/art/$f"> 31 - <div class="photo-overlay"> 32 - <div class="photo-text"> 33 - <div class="photo-title">$(title_wrapper $f)</div> 34 - <div class="photo-date">$(date -r "./art/$f" "+%d/%m — %Y")</div> 35 - </div> 36 - </div> 37 - </div> 38 - </a> 39 - XXX 40 - done 41 - %> 42 - </div> 43 - <div class="separator"></div> 44 - <a href="/" class="post-end-link">Home</a> 45 - <span>/</span> 46 - <a href="/posts" class="post-end-link">Art</a> 47 - </div> 48 - </body> 49 - </html>
art/2022.png

This is a binary file and will not be displayed.

art/PV-3.png

This is a binary file and will not be displayed.

art/antique.png

This is a binary file and will not be displayed.

art/aviator_owl.png

This is a binary file and will not be displayed.

art/b4.png

This is a binary file and will not be displayed.

art/b6.png

This is a binary file and will not be displayed.

art/b8.png

This is a binary file and will not be displayed.

art/beret_beaver.png

This is a binary file and will not be displayed.

art/bison.png

This is a binary file and will not be displayed.

art/elephant.png

This is a binary file and will not be displayed.

art/ferris.png

This is a binary file and will not be displayed.

art/forfeit.png

This is a binary file and will not be displayed.

art/goat.png

This is a binary file and will not be displayed.

art/grimoire.png

This is a binary file and will not be displayed.

art/isopod.png

This is a binary file and will not be displayed.

art/ivory_tower.png

This is a binary file and will not be displayed.

art/lapse.png

This is a binary file and will not be displayed.

art/locus.png

This is a binary file and will not be displayed.

art/merveilles.png

This is a binary file and will not be displayed.

art/monocle_rabbit.png

This is a binary file and will not be displayed.

art/moonshine.png

This is a binary file and will not be displayed.

art/notre-Dame.png

This is a binary file and will not be displayed.

art/onigiri.png

This is a binary file and will not be displayed.

art/pineapple.png

This is a binary file and will not be displayed.

art/ramen_noodles.png

This is a binary file and will not be displayed.

art/rekindled.png

This is a binary file and will not be displayed.

art/scenes_from_a_memory.png

This is a binary file and will not be displayed.

art/shark.png

This is a binary file and will not be displayed.

art/the_observer.png

This is a binary file and will not be displayed.

art/the_sect.png

This is a binary file and will not be displayed.

art/theft.png

This is a binary file and will not be displayed.

art/wane.png

This is a binary file and will not be displayed.

art/wraith.png

This is a binary file and will not be displayed.

+4
bin/dune
··· 1 + (executable 2 + (public_name site) 3 + (name main) 4 + (libraries site tw tw.html calendar))
+126
bin/home.ml
··· 1 + let sphere_css = 2 + Tw_html.rawf 3 + [ 4 + {| 5 + <style> 6 + .sphere-clip{ 7 + width:100%; 8 + aspect-ratio:1; 9 + border-radius:50%; 10 + overflow:hidden; 11 + max-height:100%; 12 + } 13 + .sphere{ 14 + --grad-start:0%; 15 + --grad-stop:80%; 16 + --rotation:0%; 17 + --fg:#fff; 18 + --bg:#000; 19 + position:relative; 20 + width:100%; 21 + height:100%; 22 + background:var(--bg); 23 + overflow:hidden; 24 + filter:url(#dither); 25 + } 26 + .sphere::before{ 27 + content:""; 28 + position:absolute; 29 + top:0; 30 + left:0; 31 + width:400%; 32 + height:100%; 33 + background:repeat-x var(--rotation) 0/50% 100% radial-gradient(circle at 50% 0,transparent var(--grad-start),var(--fg) var(--grad-stop)); 34 + mask:radial-gradient(circle at 50% 0,transparent 5%,#000 calc(var(--grad-stop) + 15%)) 0 0/50% 100%; 35 + } 36 + @media(prefers-color-scheme:dark){ 37 + .sphere{ 38 + --fg:#000; 39 + --bg:#fff; 40 + } 41 + } 42 + </style>|}; 43 + ] 44 + 45 + let dither_svg = 46 + Tw_html.rawf 47 + [ 48 + {|<svg style="position:absolute;width:0;height:0;overflow:hidden" aria-hidden="true"><defs><filter id="dither" color-interpolation-filters="sRGB" x="0" y="0" width="100%" height="100%"><feImage width="8" height="8" result="pat" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAYAAADED76LAAAA+UlEQVR42gXBERTCUABA0X/OYDAYDAZBEAyCIBgMgiAIgiAYBINgEAwGgyAIBsFgMAiCIAiCIAgGQTAYDAaDIAiCwWDwulcIIXg8HgwGA36/H4qi8Hq9sCyLtm0Rm82G0WjE5XJhvV4ThiHT6ZT7/U4QBIhut0tVVaiqSpZl9Pt9vt8vnU6HsiwRh8OB5XLJfr9nNptxPp9xXZckSbBtGyHLMs/nE9M0aZoGSZJI05ThcEhd14jdbsdkMuF2u+H7PtvtlvF4zPV6xfM8hGEYfD4fdF2nKAp6vR7v9xtN08jzHHE6nVitVsRxzGKx4Hg84jgOURQxn8/5A7oKnYRU4EpfAAAAAElFTkSuQmCC"/><feTile in="pat" result="tiled"/><feComposite operator="arithmetic" k1="0" k2="1" k3="1" k4="-0.5" in="SourceGraphic" in2="tiled"/><feComponentTransfer><feFuncR type="discrete" tableValues="0 1"/><feFuncG type="discrete" tableValues="0 1"/><feFuncB type="discrete" tableValues="0 1"/></feComponentTransfer></filter></defs></svg>|}; 49 + ] 50 + 51 + let sphere = 52 + Tw_html.( 53 + div 54 + ~at:At.[ v "class" "sphere-clip" ] 55 + [ div ~at:At.[ v "class" "sphere" ] [] ]) 56 + 57 + let about = 58 + Tw_html.( 59 + div 60 + ~tw:Tw.[ flex; flex_row; p 2; mt 12; gap 12 ] 61 + [ 62 + dither_svg; 63 + div 64 + ~tw:Tw.[ aspect_ratio 1 1; w 48; flex; items_center; justify_center ] 65 + [ sphere ]; 66 + div 67 + ~tw:Tw.[ text_justify ] 68 + [ 69 + p 70 + [ 71 + txt 72 + "I'm Akshay, programmer, pixel-artist & programming-language \ 73 + enthusiast."; 74 + ]; 75 + p 76 + [ 77 + txt "I am currently building "; 78 + a ~at:At.[ href "https://tangled.org" ] [ txt "Tangled" ]; 79 + txt ", a new social-enabled code-collaboration platform."; 80 + ]; 81 + p [ txt "Reach out at oppili@libera.chat." ]; 82 + ]; 83 + ]) 84 + 85 + let posts_p posts = 86 + Tw_html.( 87 + div 88 + ~tw:Tw.[ space_y 4. ] 89 + [ 90 + div 91 + ~tw:Tw.[ flex; items_center; justify_between ] 92 + [ 93 + h2 [ txt "posts" ]; 94 + a ~tw:Tw.[ mt 10 ] ~at:At.[ href "/posts" ] [ txt "view all" ]; 95 + ]; 96 + posts; 97 + ]) 98 + 99 + let weeklies_p posts = 100 + Tw_html.( 101 + div 102 + [ 103 + div 104 + ~tw:Tw.[ flex; items_center; justify_between ] 105 + [ 106 + h2 [ txt "weeklies" ]; 107 + a ~tw:Tw.[ mt 10 ] ~at:At.[ href "/weeklies" ] [ txt "view all" ]; 108 + ]; 109 + posts; 110 + ]) 111 + 112 + let render ~posts_preview ~weeklies_preview = 113 + Tw_html.( 114 + div 115 + ~tw:Tw.[ max_w_xl; mx_auto ] 116 + [ 117 + sphere_css; 118 + about; 119 + div [ posts_p posts_preview; weeklies_p weeklies_preview ]; 120 + ]) 121 + 122 + let build ~posts_preview ~weeklies_preview = 123 + Site.Layout.page ~title:"oppi.li" (render ~posts_preview ~weeklies_preview) 124 + 125 + let write ~out_dir p = Site.Category.write_page ~out_dir "" p 126 + let css p = snd (Tw_html.css p)
+24
bin/main.ml
··· 1 + let () = 2 + let posts = Posts.Cat.build ~path:"content/posts" in 3 + Printf.printf "[OK] %s: %d\n%!" (Posts.Cat.name posts) (Posts.Cat.count posts); 4 + let weeklies = Weeklies.Cat.build ~path:"content/weeklies" in 5 + Printf.printf "[OK] %s: %d\n%!" 6 + (Weeklies.Cat.name weeklies) 7 + (Weeklies.Cat.count weeklies); 8 + let home = 9 + Home.build 10 + ~posts_preview:(Posts.Cat.preview ~limit:3 posts) 11 + ~weeklies_preview:(Weeklies.Cat.preview ~limit:1 weeklies) 12 + in 13 + Posts.Cat.write ~out_dir:"_site" posts; 14 + Weeklies.Cat.write ~out_dir:"_site" weeklies; 15 + Home.write ~out_dir:"_site" home; 16 + let css = 17 + Tw.Css.concat 18 + [ Posts.Cat.css posts; Weeklies.Cat.css weeklies; Home.css home ] 19 + in 20 + let css_path = "_site/style.css" in 21 + Out_channel.with_open_text css_path (fun oc -> 22 + Out_channel.output_string oc 23 + (Tw.Css.to_string ~minify:true ~optimize:true css)); 24 + Printf.printf "[OK] wrote %s\n%!" css_path
+66
bin/posts.ml
··· 1 + module Index (P : Site.Content.Page) : 2 + Site.Category.Index with type page = P.t = struct 3 + type page = P.t 4 + 5 + let compare_date a b = 6 + match (P.date a, P.date b) with 7 + | Some da, Some db -> CalendarLib.Date.compare db da 8 + | Some _, None -> -1 9 + | None, Some _ -> 1 10 + | None, None -> 0 11 + 12 + let view_row ?(base = "") post = 13 + Tw_html.( 14 + div 15 + ~tw:Tw.[ flex ] 16 + [ 17 + a ~at:At.[ href (base ^ P.slug post) ] [ txt (P.title post) ]; 18 + span 19 + ~tw:Tw.[ flex_grow; text_right; tabular_nums ] 20 + [ 21 + txt 22 + (Option.map 23 + (CalendarLib.Printer.Date.sprint "%d.%m.%Y") 24 + (P.date post) 25 + |> Option.value ~default:""); 26 + ]; 27 + ]) 28 + 29 + let view_rows ?(base = "") posts = List.map (view_row ~base) posts 30 + 31 + let render posts = 32 + let sorted = List.sort compare_date posts in 33 + let years = 34 + let newest = List.nth sorted 0 |> P.date |> Option.get in 35 + let oldest = 36 + List.nth sorted (List.length sorted - 1) |> P.date |> Option.get 37 + in 38 + CalendarLib.Date.year newest - CalendarLib.Date.year oldest 39 + in 40 + Tw_html.( 41 + div 42 + ~tw:Tw.[ max_w_xl; mx_auto ] 43 + [ 44 + h1 ~tw:Tw.[ text_center ] [ txt "posts" ]; 45 + div 46 + ~tw:Tw.[ text_center; space_x 2. ] 47 + [ 48 + span [ txt (Printf.sprintf "%d entries" (List.length posts)) ]; 49 + span [ txt "\u{00B7}" ]; 50 + span [ txt (Printf.sprintf "%d years" years) ]; 51 + ]; 52 + div ~tw:Tw.[ mt 4 ] (view_rows sorted); 53 + ]) 54 + 55 + let preview ?(base = "") ~limit posts = 56 + let recent = List.sort compare_date posts |> List.take limit in 57 + Tw_html.(div (view_rows ~base recent)) 58 + end 59 + 60 + module Cat = 61 + Site.Category.Make 62 + (struct 63 + let name = "posts" 64 + end) 65 + (Site.Content.MarkdownPage) 66 + (Index (Site.Content.MarkdownPage))
+109
bin/weeklies.ml
··· 1 + module Index (P : Site.Content.Page) : 2 + Site.Category.Index with type page = P.t = struct 3 + type page = P.t 4 + 5 + let year_week p = 6 + match String.split_on_char '-' (P.slug p) with 7 + | [ y; w ] -> ( 8 + try Some (int_of_string y, int_of_string w) with _ -> None) 9 + | _ -> None 10 + 11 + let render posts = 12 + let tbl : (int, (int, P.t) Hashtbl.t) Hashtbl.t = Hashtbl.create 8 in 13 + List.iter 14 + (fun p -> 15 + match year_week p with 16 + | None -> () 17 + | Some (y, w) -> 18 + let by_week = 19 + match Hashtbl.find_opt tbl y with 20 + | Some t -> t 21 + | None -> 22 + let t = Hashtbl.create 52 in 23 + Hashtbl.add tbl y t; 24 + t 25 + in 26 + Hashtbl.replace by_week w p) 27 + posts; 28 + let years = 29 + Hashtbl.fold (fun y _ acc -> y :: acc) tbl [] 30 + |> List.sort (fun a b -> compare b a) 31 + in 32 + let by_year year = 33 + let by_week = Hashtbl.find tbl year in 34 + let grid = 35 + Tw_html.( 36 + div 37 + ~at:At.[ style "column-gap: 1ch" ] 38 + ~tw:Tw.[ grid; grid_cols 4; mt 2; tabular_nums; w_fit ] 39 + (List.init 52 (fun i -> 40 + let w = i + 1 in 41 + match Hashtbl.find_opt by_week w with 42 + | None -> span ~tw:Tw.[ text_right ] [ txt (string_of_int w) ] 43 + | Some p -> 44 + a 45 + ~tw:Tw.[ text_right ] 46 + ~at:At.[ href (P.slug p) ] 47 + [ txt (string_of_int w) ]))) 48 + in 49 + Tw_html.[ div [ h2 [ txt (string_of_int year) ]; grid ] ] 50 + in 51 + let count = List.length posts in 52 + let weeks_span = 53 + let birth = CalendarLib.Date.make 2000 12 30 in 54 + let today = CalendarLib.Date.today () in 55 + CalendarLib.Date.Period.nb_days (CalendarLib.Date.sub today birth) / 7 56 + in 57 + Tw_html.( 58 + div 59 + ~tw:Tw.[ max_w_xl; mx_auto ] 60 + [ 61 + h1 ~tw:Tw.[ text_center ] [ txt "weeklies" ]; 62 + p 63 + ~tw:Tw.[ text_center ] 64 + [ 65 + txt (Printf.sprintf "%d of %d weeks documented" count weeks_span); 66 + ]; 67 + div ~tw:Tw.[ grid; grid_cols 3; mt 4 ] (List.concat_map by_year years); 68 + ]) 69 + 70 + let preview ?(base = "") ~limit posts = 71 + let recent = 72 + List.filter_map 73 + (fun p -> 74 + match year_week p with Some (y, w) -> Some (y, w, p) | None -> None) 75 + posts 76 + |> List.sort (fun (ya, wa, _) (yb, wb, _) -> compare (yb, wb) (ya, wa)) 77 + |> List.take limit 78 + in 79 + let entry (y, w, post) = 80 + let toc = P.toc post in 81 + Tw_html.( 82 + div 83 + ~tw:Tw.[ flex; flex_row; items_start ] 84 + [ 85 + div 86 + ~tw:Tw.[ flex; flex_col; mt 4; items_center ] 87 + [ 88 + a 89 + ~tw:Tw.[ text_lg ] 90 + ~at:At.[ href (base ^ P.slug post) ] 91 + [ txt (string_of_int w) ]; 92 + span ~tw:Tw.[ text_xs ] [ txt (string_of_int y) ]; 93 + ]; 94 + ul 95 + (List.take 3 toc 96 + |> List.map (fun (item : Site.Content.toc_item) -> 97 + li [ txt item.text ])); 98 + ]) 99 + in 100 + Tw_html.(div (List.map entry recent)) 101 + end 102 + 103 + module Cat = 104 + Site.Category.Make 105 + (struct 106 + let name = "weeklies" 107 + end) 108 + (Site.Content.MarkdownPage) 109 + (Index (Site.Content.MarkdownPage))
+82
content/weeklies/2026-16.md
··· 1 + --- 2 + date: 19-04-2026 3 + --- 4 + ## FP Launchpad at IIT Madras 5 + 6 + I had the pleasure of attending the inaugration of [FP 7 + Launchpad](https://fplaunchpad.org/), courtesy of [KC 8 + Sivaramakrishnan](https://kcsrk.info/) and [Anil 9 + Madhavapeddy](https://anil.recoil.org). It was a packed 10 + schedule with back-to-back presentations. My personal 11 + favourites: 12 + 13 + - [Towards verifiable governance with LLMs and 14 + Lean](https://fplaunchpad.org/2026/03/30/fp-launchpad-kickoff.html#raghavan) 15 + - [A Programming Language for Lightweight 16 + Diagramming](https://fplaunchpad.org/2026/03/30/fp-launchpad-kickoff.html#krishnamurthi) 17 + 18 + It was truly rousing to see FP grab hold in Indian 19 + universities. I briefly attempted to run a series of [Scheme 20 + & Haskell](https://github.com/DSCRV/lisk) tutorials for my 21 + juniors at university several years ago! 22 + 23 + ## Renewed interest in OCaml 24 + 25 + After attending FP Launchpad, my interest in OCaml was at an 26 + all time high. So I've rewritten [the ugly bash 27 + program](https://tangled.org/oppi.li/site/blob/dbc61fb36b60a574dd8e99af7caf8abfb2be2638/generate.sh) 28 + that was used to generate this website [in 29 + OCaml](https://tangled.org/oppi.li/site), using the 30 + wonderful [tw](https://github.com/samoht/tw) library by 31 + [Thomas Gazagnaire](https://gazagnaire.org/). I've now added 32 + a section for [weeklies](/weeklies), to get myself to write 33 + more often. 34 + 35 + I've also been poking at 36 + [bruit](https://tangled.org/patrick.sirref.org/bruit) to 37 + [have it work on the 38 + web](https://tangled.org/oppi.li/bruit/commit/87bdc3254ee028007805e6b7135fa0da83287fb9), 39 + so I can eventually show-off my OCaml implementation of 40 + [Modal](https://wryl.tech/projects/modal.html) on a [Tangled 41 + site](https://docs.tangled.org/hosting-websites-on-tangled.html#hosting-websites-on-tangled). 42 + 43 + I've started familiarizing myself with OCaml's 44 + [metaprogramming 45 + ecosystem](https://github.com/ocaml-ppx/ppxlib) to emit 46 + [spytial diagrams](https://github.com/sidprasad/spytial) 47 + from OCaml data structures. It is ever so slightly different 48 + from 49 + [proc_macro2](https://docs.rs/proc-macro2/latest/proc_macro2/) 50 + in that it operates on a full 51 + [Parsetree](https://ocaml.org/manual/5.4/api/compilerlibref/Parsetree.html) 52 + and not a plain 53 + [TokenStream](https://docs.rs/proc-macro2/latest/proc_macro2/struct.TokenStream.html). 54 + 55 + ## Building a web-of-trust 56 + 57 + I've been taking a few (cautious) steps towards implementing 58 + [vouching](https://github.com/mitchellh/vouch/) on Tangled. 59 + More on that next week! 60 + 61 + ## Improving my music setup 62 + 63 + [tinysub](https://tangled.org/devins.page/tinysub), a 64 + subsonic-compatible web player popped up on my Tangled feed, 65 + and I instantly took to setting it up on my homeserver. 66 + Using [monolith](https://github.com/Y2Z/monolith), I was 67 + able to build it down to a single 100kB HTML file, assets 68 + included. 69 + 70 + I then rigged up my navidrome server to scrobble listens 71 + directly to my 72 + [PDS](https://atproto.com/guides/self-hosting#pds) using the 73 + [rocksky 74 + CLI](https://tangled.org/oppi.li/rocksky/commit/47babb790cd34d70c7a3638bf8d145013cfef7fd#flake.nix-N42). 75 + My scrobbles are now aggregated by 76 + [rocksky.app](https://rocksky.app) and 77 + [teal.fm](https://teal.fm). 78 + 79 + ## Travel 80 + 81 + I am now back in London! I've spent the weekend trying to 82 + revive my houseplants... with little luck.
-1
docs/CNAME
··· 1 - peppe.rs
docs/art/2022.png

This is a binary file and will not be displayed.

docs/art/PV-3.png

This is a binary file and will not be displayed.

docs/art/antique.png

This is a binary file and will not be displayed.

docs/art/aviator_owl.png

This is a binary file and will not be displayed.

docs/art/b4.png

This is a binary file and will not be displayed.

docs/art/b6.png

This is a binary file and will not be displayed.

docs/art/b8.png

This is a binary file and will not be displayed.

docs/art/beret_beaver.png

This is a binary file and will not be displayed.

docs/art/bison.png

This is a binary file and will not be displayed.

docs/art/elephant.png

This is a binary file and will not be displayed.

docs/art/ferris.png

This is a binary file and will not be displayed.

docs/art/forfeit.png

This is a binary file and will not be displayed.

docs/art/goat.png

This is a binary file and will not be displayed.

docs/art/grimoire.png

This is a binary file and will not be displayed.

-395
docs/art/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Art · oppi.li"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Art · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <a href="/" class="post-end-link">Home</a> 19 - <span>/</span> 20 - <a href="/posts" class="post-end-link">Art</a> 21 - <h1>Art</h1> 22 - <div class="separator"></div> 23 - <div id="photos"> 24 - <a href="/art/theft.png"> 25 - <div class="photo-container"> 26 - <img src="/art/theft.png"> 27 - <div class="photo-overlay"> 28 - <div class="photo-text"> 29 - <div class="photo-title">Theft</div> 30 - <div class="photo-date">22/10 — 2023</div> 31 - </div> 32 - </div> 33 - </div> 34 - </a> 35 - <a href="/art/wane.png"> 36 - <div class="photo-container"> 37 - <img src="/art/wane.png"> 38 - <div class="photo-overlay"> 39 - <div class="photo-text"> 40 - <div class="photo-title">Wane</div> 41 - <div class="photo-date">01/07 — 2023</div> 42 - </div> 43 - </div> 44 - </div> 45 - </a> 46 - <a href="/art/merveilles.png"> 47 - <div class="photo-container"> 48 - <img src="/art/merveilles.png"> 49 - <div class="photo-overlay"> 50 - <div class="photo-text"> 51 - <div class="photo-title">Merveilles</div> 52 - <div class="photo-date">28/05 — 2023</div> 53 - </div> 54 - </div> 55 - </div> 56 - </a> 57 - <a href="/art/forfeit.png"> 58 - <div class="photo-container"> 59 - <img src="/art/forfeit.png"> 60 - <div class="photo-overlay"> 61 - <div class="photo-text"> 62 - <div class="photo-title">Forfeit</div> 63 - <div class="photo-date">20/04 — 2023</div> 64 - </div> 65 - </div> 66 - </div> 67 - </a> 68 - <a href="/art/lapse.png"> 69 - <div class="photo-container"> 70 - <img src="/art/lapse.png"> 71 - <div class="photo-overlay"> 72 - <div class="photo-text"> 73 - <div class="photo-title">Lapse</div> 74 - <div class="photo-date">12/02 — 2023</div> 75 - </div> 76 - </div> 77 - </div> 78 - </a> 79 - <a href="/art/ferris.png"> 80 - <div class="photo-container"> 81 - <img src="/art/ferris.png"> 82 - <div class="photo-overlay"> 83 - <div class="photo-text"> 84 - <div class="photo-title">Ferris</div> 85 - <div class="photo-date">28/08 — 2022</div> 86 - </div> 87 - </div> 88 - </div> 89 - </a> 90 - <a href="/art/locus.png"> 91 - <div class="photo-container"> 92 - <img src="/art/locus.png"> 93 - <div class="photo-overlay"> 94 - <div class="photo-text"> 95 - <div class="photo-title">Locus</div> 96 - <div class="photo-date">08/06 — 2022</div> 97 - </div> 98 - </div> 99 - </div> 100 - </a> 101 - <a href="/art/ivory_tower.png"> 102 - <div class="photo-container"> 103 - <img src="/art/ivory_tower.png"> 104 - <div class="photo-overlay"> 105 - <div class="photo-text"> 106 - <div class="photo-title">Ivory Tower</div> 107 - <div class="photo-date">02/06 — 2022</div> 108 - </div> 109 - </div> 110 - </div> 111 - </a> 112 - <a href="/art/the_sect.png"> 113 - <div class="photo-container"> 114 - <img src="/art/the_sect.png"> 115 - <div class="photo-overlay"> 116 - <div class="photo-text"> 117 - <div class="photo-title">The Sect</div> 118 - <div class="photo-date">02/06 — 2022</div> 119 - </div> 120 - </div> 121 - </div> 122 - </a> 123 - <a href="/art/elephant.png"> 124 - <div class="photo-container"> 125 - <img src="/art/elephant.png"> 126 - <div class="photo-overlay"> 127 - <div class="photo-text"> 128 - <div class="photo-title">Elephant</div> 129 - <div class="photo-date">20/05 — 2022</div> 130 - </div> 131 - </div> 132 - </div> 133 - </a> 134 - <a href="/art/2022.png"> 135 - <div class="photo-container"> 136 - <img src="/art/2022.png"> 137 - <div class="photo-overlay"> 138 - <div class="photo-text"> 139 - <div class="photo-title">2022</div> 140 - <div class="photo-date">12/03 — 2022</div> 141 - </div> 142 - </div> 143 - </div> 144 - </a> 145 - <a href="/art/scenes_from_a_memory.png"> 146 - <div class="photo-container"> 147 - <img src="/art/scenes_from_a_memory.png"> 148 - <div class="photo-overlay"> 149 - <div class="photo-text"> 150 - <div class="photo-title">Scenes From A Memory</div> 151 - <div class="photo-date">20/12 — 2021</div> 152 - </div> 153 - </div> 154 - </div> 155 - </a> 156 - <a href="/art/b8.png"> 157 - <div class="photo-container"> 158 - <img src="/art/b8.png"> 159 - <div class="photo-overlay"> 160 - <div class="photo-text"> 161 - <div class="photo-title">B8</div> 162 - <div class="photo-date">03/10 — 2021</div> 163 - </div> 164 - </div> 165 - </div> 166 - </a> 167 - <a href="/art/goat.png"> 168 - <div class="photo-container"> 169 - <img src="/art/goat.png"> 170 - <div class="photo-overlay"> 171 - <div class="photo-text"> 172 - <div class="photo-title">Goat</div> 173 - <div class="photo-date">22/08 — 2021</div> 174 - </div> 175 - </div> 176 - </div> 177 - </a> 178 - <a href="/art/b6.png"> 179 - <div class="photo-container"> 180 - <img src="/art/b6.png"> 181 - <div class="photo-overlay"> 182 - <div class="photo-text"> 183 - <div class="photo-title">B6</div> 184 - <div class="photo-date">09/07 — 2021</div> 185 - </div> 186 - </div> 187 - </div> 188 - </a> 189 - <a href="/art/PV-3.png"> 190 - <div class="photo-container"> 191 - <img src="/art/PV-3.png"> 192 - <div class="photo-overlay"> 193 - <div class="photo-text"> 194 - <div class="photo-title">PV-3</div> 195 - <div class="photo-date">26/06 — 2021</div> 196 - </div> 197 - </div> 198 - </div> 199 - </a> 200 - <a href="/art/rekindled.png"> 201 - <div class="photo-container"> 202 - <img src="/art/rekindled.png"> 203 - <div class="photo-overlay"> 204 - <div class="photo-text"> 205 - <div class="photo-title">Rekindled</div> 206 - <div class="photo-date">10/06 — 2021</div> 207 - </div> 208 - </div> 209 - </div> 210 - </a> 211 - <a href="/art/bison.png"> 212 - <div class="photo-container"> 213 - <img src="/art/bison.png"> 214 - <div class="photo-overlay"> 215 - <div class="photo-text"> 216 - <div class="photo-title">Bison</div> 217 - <div class="photo-date">04/06 — 2021</div> 218 - </div> 219 - </div> 220 - </div> 221 - </a> 222 - <a href="/art/notre-Dame.png"> 223 - <div class="photo-container"> 224 - <img src="/art/notre-Dame.png"> 225 - <div class="photo-overlay"> 226 - <div class="photo-text"> 227 - <div class="photo-title">Notre-Dame</div> 228 - <div class="photo-date">29/05 — 2021</div> 229 - </div> 230 - </div> 231 - </div> 232 - </a> 233 - <a href="/art/pineapple.png"> 234 - <div class="photo-container"> 235 - <img src="/art/pineapple.png"> 236 - <div class="photo-overlay"> 237 - <div class="photo-text"> 238 - <div class="photo-title">Pineapple</div> 239 - <div class="photo-date">28/05 — 2021</div> 240 - </div> 241 - </div> 242 - </div> 243 - </a> 244 - <a href="/art/b4.png"> 245 - <div class="photo-container"> 246 - <img src="/art/b4.png"> 247 - <div class="photo-overlay"> 248 - <div class="photo-text"> 249 - <div class="photo-title">B4</div> 250 - <div class="photo-date">21/05 — 2021</div> 251 - </div> 252 - </div> 253 - </div> 254 - </a> 255 - <a href="/art/isopod.png"> 256 - <div class="photo-container"> 257 - <img src="/art/isopod.png"> 258 - <div class="photo-overlay"> 259 - <div class="photo-text"> 260 - <div class="photo-title">Isopod</div> 261 - <div class="photo-date">21/05 — 2021</div> 262 - </div> 263 - </div> 264 - </div> 265 - </a> 266 - <a href="/art/wraith.png"> 267 - <div class="photo-container"> 268 - <img src="/art/wraith.png"> 269 - <div class="photo-overlay"> 270 - <div class="photo-text"> 271 - <div class="photo-title">Wraith</div> 272 - <div class="photo-date">13/05 — 2021</div> 273 - </div> 274 - </div> 275 - </div> 276 - </a> 277 - <a href="/art/antique.png"> 278 - <div class="photo-container"> 279 - <img src="/art/antique.png"> 280 - <div class="photo-overlay"> 281 - <div class="photo-text"> 282 - <div class="photo-title">Antique</div> 283 - <div class="photo-date">07/05 — 2021</div> 284 - </div> 285 - </div> 286 - </div> 287 - </a> 288 - <a href="/art/grimoire.png"> 289 - <div class="photo-container"> 290 - <img src="/art/grimoire.png"> 291 - <div class="photo-overlay"> 292 - <div class="photo-text"> 293 - <div class="photo-title">Grimoire</div> 294 - <div class="photo-date">05/05 — 2021</div> 295 - </div> 296 - </div> 297 - </div> 298 - </a> 299 - <a href="/art/aviator_owl.png"> 300 - <div class="photo-container"> 301 - <img src="/art/aviator_owl.png"> 302 - <div class="photo-overlay"> 303 - <div class="photo-text"> 304 - <div class="photo-title">Aviator Owl</div> 305 - <div class="photo-date">30/04 — 2021</div> 306 - </div> 307 - </div> 308 - </div> 309 - </a> 310 - <a href="/art/beret_beaver.png"> 311 - <div class="photo-container"> 312 - <img src="/art/beret_beaver.png"> 313 - <div class="photo-overlay"> 314 - <div class="photo-text"> 315 - <div class="photo-title">Beret Beaver</div> 316 - <div class="photo-date">30/04 — 2021</div> 317 - </div> 318 - </div> 319 - </div> 320 - </a> 321 - <a href="/art/monocle_rabbit.png"> 322 - <div class="photo-container"> 323 - <img src="/art/monocle_rabbit.png"> 324 - <div class="photo-overlay"> 325 - <div class="photo-text"> 326 - <div class="photo-title">Monocle Rabbit</div> 327 - <div class="photo-date">30/04 — 2021</div> 328 - </div> 329 - </div> 330 - </div> 331 - </a> 332 - <a href="/art/ramen_noodles.png"> 333 - <div class="photo-container"> 334 - <img src="/art/ramen_noodles.png"> 335 - <div class="photo-overlay"> 336 - <div class="photo-text"> 337 - <div class="photo-title">Ramen Noodles</div> 338 - <div class="photo-date">25/03 — 2021</div> 339 - </div> 340 - </div> 341 - </div> 342 - </a> 343 - <a href="/art/shark.png"> 344 - <div class="photo-container"> 345 - <img src="/art/shark.png"> 346 - <div class="photo-overlay"> 347 - <div class="photo-text"> 348 - <div class="photo-title">Shark</div> 349 - <div class="photo-date">21/02 — 2021</div> 350 - </div> 351 - </div> 352 - </div> 353 - </a> 354 - <a href="/art/moonshine.png"> 355 - <div class="photo-container"> 356 - <img src="/art/moonshine.png"> 357 - <div class="photo-overlay"> 358 - <div class="photo-text"> 359 - <div class="photo-title">Moonshine</div> 360 - <div class="photo-date">24/04 — 2020</div> 361 - </div> 362 - </div> 363 - </div> 364 - </a> 365 - <a href="/art/onigiri.png"> 366 - <div class="photo-container"> 367 - <img src="/art/onigiri.png"> 368 - <div class="photo-overlay"> 369 - <div class="photo-text"> 370 - <div class="photo-title">Onigiri</div> 371 - <div class="photo-date">08/04 — 2020</div> 372 - </div> 373 - </div> 374 - </div> 375 - </a> 376 - <a href="/art/the_observer.png"> 377 - <div class="photo-container"> 378 - <img src="/art/the_observer.png"> 379 - <div class="photo-overlay"> 380 - <div class="photo-text"> 381 - <div class="photo-title">The Observer</div> 382 - <div class="photo-date">16/01 — 2020</div> 383 - </div> 384 - </div> 385 - </div> 386 - </a> 387 - 388 - </div> 389 - <div class="separator"></div> 390 - <a href="/" class="post-end-link">Home</a> 391 - <span>/</span> 392 - <a href="/posts" class="post-end-link">Art</a> 393 - </div> 394 - </body> 395 - </html>
docs/art/isopod.png

This is a binary file and will not be displayed.

docs/art/ivory_tower.png

This is a binary file and will not be displayed.

docs/art/lapse.png

This is a binary file and will not be displayed.

docs/art/locus.png

This is a binary file and will not be displayed.

docs/art/merveilles.png

This is a binary file and will not be displayed.

docs/art/monocle_rabbit.png

This is a binary file and will not be displayed.

docs/art/moonshine.png

This is a binary file and will not be displayed.

docs/art/notre-Dame.png

This is a binary file and will not be displayed.

docs/art/onigiri.png

This is a binary file and will not be displayed.

docs/art/pineapple.png

This is a binary file and will not be displayed.

docs/art/ramen_noodles.png

This is a binary file and will not be displayed.

docs/art/rekindled.png

This is a binary file and will not be displayed.

docs/art/scenes_from_a_memory.png

This is a binary file and will not be displayed.

docs/art/shark.png

This is a binary file and will not be displayed.

docs/art/the_observer.png

This is a binary file and will not be displayed.

docs/art/the_sect.png

This is a binary file and will not be displayed.

docs/art/theft.png

This is a binary file and will not be displayed.

docs/art/wane.png

This is a binary file and will not be displayed.

docs/art/wraith.png

This is a binary file and will not be displayed.

docs/favicon.png

This is a binary file and will not be displayed.

-117
docs/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="alternate" type="application/atom+xml" title="oppili's micro musings" href="/index.xml"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="oppili's site"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="oppili's micro musings"> 13 - <meta property="og:url" content="https://oppi.li"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>oppi.li</title> 16 - <h1 class="heading">oppili</h1> 17 - <h4 class="subheading">previously peppe.rs</h4> 18 - <body> 19 - <div class="posts"> 20 - <div class="post"> 21 - 22 - <div class="intro"> 23 - Hi. 24 - <div class="hot-links"> 25 - <a href="/index.xml" class="feed-button">Subscribe</a> 26 - </div> 27 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 28 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 29 - <p>Reach out at oppili@libera.chat.</p> 30 - </div> 31 - 32 - <table> 33 - <tr><td class="recent-heading"><span class="recent-heading">Recent Posts</span></td></tr> 34 - 35 - <tr> 36 - <td class=table-post> 37 - <div class="date"> 38 - 31/07 — 2025 39 - </div> 40 - <a href="/posts/mounting_the_atmosphere" class="post-link"> 41 - <span class="post-link">Mounting The Atmosphere</span> 42 - </a> 43 - </td> 44 - <td class=table-stats> 45 - <span class="stats-number"> 46 - 4.3 47 - </span> 48 - <span class=stats-unit>min</span> 49 - </td> 50 - </tr> 51 - 52 - <tr> 53 - <td class=table-post> 54 - <div class="date"> 55 - 24/05 — 2025 56 - </div> 57 - <a href="/posts/configuring_jujutsu" class="post-link"> 58 - <span class="post-link">Configuring Jujutsu</span> 59 - </a> 60 - </td> 61 - <td class=table-stats> 62 - <span class="stats-number"> 63 - 9.5 64 - </span> 65 - <span class=stats-unit>min</span> 66 - </td> 67 - </tr> 68 - 69 - <tr> 70 - <td class=table-post> 71 - <div class="date"> 72 - 21/05 — 2025 73 - </div> 74 - <a href="/posts/tales_from_mainframe_modernization" class="post-link"> 75 - <span class="post-link">Tales From Mainframe Modernization</span> 76 - </a> 77 - </td> 78 - <td class=table-stats> 79 - <span class="stats-number"> 80 - 3.9 81 - </span> 82 - <span class=stats-unit>min</span> 83 - </td> 84 - </tr> 85 - <tr><td><a href="/posts" class="post-end-link">More ⟶ </a></td></tr> 86 - <tr><td class="recent-heading"><span class="recent-heading">Recent Art</span></td></tr> 87 - 88 - <tr> 89 - <td class=table-post> 90 - <div class="date"> 91 - 22/10 — 2023 92 - </div> 93 - <a href="/art/theft.png" class="post-link"> 94 - <span class="post-link">Theft</span> 95 - </a> 96 - </td> 97 - <td class="table-stats"> 98 - <a href="/art/theft.png"> 99 - <img src="/art/theft.png" height="50px"> 100 - </a> 101 - </td> 102 - </tr> 103 - <tr><td><a href="/art" class="post-end-link">More ⟶ </a></td></tr> 104 - </table> 105 - <div class="separator"></div> 106 - <div class="footer"> 107 - <a href="https://tangled.sh/@oppi.li">Code</a> · 108 - <a href="https://bsky.app/profile/oppi.li">Bluesky</a> · 109 - <a href="mailto:me@oppi.li">Mail</a> 110 - <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/"> 111 - <img class="footimgs" src="https://d33wubrfki0l68.cloudfront.net/94387e9d77fbc8b4360db81e72603ecba3df94a7/632bc/static/cc.svg"> 112 - </a> 113 - </div> 114 - </div> 115 - </div> 116 - </body> 117 - </html>
-4390
docs/index.xml
··· 1 - <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> 2 - <channel> 3 - <title>oppiliappan's μblog</title> 4 - <link>https://oppi.li</link> 5 - <description>programming, design, software</description> 6 - <atom:link href="https://oppi.li/index.xml" rel="self" type="application/rss+xml" /> 7 - <image> 8 - <title>oppiliappan's μblog</title> 9 - <url>https://cdn.oppi.li/n.png</url> 10 - <link>https://oppi.li</link> 11 - </image> 12 - <language>en-us</language> 13 - <copyright>Creative Commons BY-NC-SA 4.0</copyright> 14 - <item> 15 - <title>Mounting The Atmosphere</title> 16 - <description>&lt;p&gt;&lt;a href="https://tangled.sh/@oppi.li/pdsfs"&gt;pdsfs&lt;/a&gt; is a tool that 17 - mounts &lt;a href="https://atproto.com"&gt;atproto&lt;/a&gt; PDS repositories as a 18 - &lt;a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace"&gt;FUSE&lt;/a&gt; 19 - filesystem.&lt;/p&gt; 20 - &lt;p&gt;A &lt;a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace"&gt;PDS 21 - repository&lt;/a&gt; contains all data published by a user to the atmosphere. 22 - It is exportable as a CAR (content-addressable archive) file. pdsfs is a 23 - tool that mounts this CAR file as a readonly-FUSE filesystem, allowing 24 - quick and easy exploration.&lt;/p&gt; 25 - &lt;p&gt;To motivate the need for such a program, we could begin by mounting a 26 - repository:&lt;/p&gt; 27 - &lt;pre&gt;&lt;code&gt;λ pdsfs oppi.li 28 - mounted at &amp;quot;mnt&amp;quot; 29 - hit enter to unmount and exit...&lt;/code&gt;&lt;/pre&gt; 30 - &lt;p&gt;oppi.li is my handle in the atmosphere. The tool does some hardwork 31 - to determine the location of my PDS repository given my handle, but that 32 - is not important. Let’s have a look around:&lt;/p&gt; 33 - &lt;pre&gt;&lt;code&gt;λ ls mnt/ 34 - did:plc:qfpnj4og54vl56wngdriaxug/&lt;/code&gt;&lt;/pre&gt; 35 - &lt;p&gt;The &lt;code&gt;did:plc:stuff&lt;/code&gt; is my &lt;a 36 - href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace"&gt;DID&lt;/a&gt;. 37 - Digging deeper:&lt;/p&gt; 38 - &lt;pre&gt;&lt;code&gt;λ ls mnt/did\:plc\:qfpnj4og54vl56wngdriaxug/ 39 - app.bsky.actor.profile/ place.stream.chat.message/ 40 - app.bsky.actor.status/ place.stream.chat.profile/ 41 - app.bsky.feed.generator/ place.stream.key/ 42 - app.bsky.feed.like/ place.stream.livestream/ 43 - app.bsky.feed.post/ sh.tangled.actor.profile/ 44 - app.bsky.feed.repost/ sh.tangled.feed.reaction/ 45 - app.bsky.graph.block/ sh.tangled.feed.star/ 46 - app.bsky.graph.follow/ sh.tangled.graph.follow/ 47 - app.rocksky.album/ sh.tangled.knot/ 48 - app.rocksky.artist/ sh.tangled.knot.member/ 49 - . 50 - . 51 - .&lt;/code&gt;&lt;/pre&gt; 52 - &lt;p&gt;We have some data from the repository now. These are “collections”. 53 - If I want to publish a post to Bluesky, I would write content to the 54 - &lt;code&gt;app.bsky.feed.post&lt;/code&gt; collection in my PDS. This will then be 55 - indexed by a Bluesky appview (such as &lt;a 56 - href="https://bsky.app"&gt;bsky.app&lt;/a&gt; or &lt;a 57 - href="https://zeppelin.social"&gt;zeppelin.social&lt;/a&gt;) and show up under my 58 - profile there.&lt;/p&gt; 59 - &lt;p&gt;pdsfs is kind enough to deserialize the in the PDS repository (stored 60 - as CBOR normally) to JSON on the filesystem:&lt;/p&gt; 61 - &lt;pre&gt;&lt;code&gt;λ cat sh.tangled.repo/3ljidbevrjh22 | jq 62 - { 63 - &amp;quot;$type&amp;quot;: &amp;quot;sh.tangled.repo&amp;quot;, 64 - &amp;quot;addedAt&amp;quot;: &amp;quot;2025-03-03T16:04:13Z&amp;quot;, 65 - &amp;quot;knot&amp;quot;: &amp;quot;knot1.tangled.sh&amp;quot;, 66 - &amp;quot;name&amp;quot;: &amp;quot;hello-world&amp;quot;, 67 - &amp;quot;owner&amp;quot;: &amp;quot;did:plc:3danwc67lo7obz2fmdg6jxcr&amp;quot; 68 - }&lt;/code&gt;&lt;/pre&gt; 69 - &lt;p&gt;Thanks pdsfs!&lt;/p&gt; 70 - &lt;p&gt;I publish my music listening habits to my PDS to the 71 - &lt;code&gt;app.rocksky.scrobble&lt;/code&gt; collection, because &lt;a 72 - href="https://rocksky.app"&gt;Rocksky&lt;/a&gt; recognizes and indexes this 73 - collection. I have wired up my personal navidrome instance to write data 74 - of this form into my PDS everytime I listen to a track. Here are my top 75 - artists in order:&lt;/p&gt; 76 - &lt;pre&gt;&lt;code&gt;λ jq -r &amp;#39;.artist&amp;#39; app.rocksky.scrobble/* | sort | uniq -c | sort -nr 77 - 117 Thank You Scientist 78 - 45 FKJ 79 - 34 Covet 80 - 33 VOLA 81 - 23 Sam Cooke 82 - 22 Dark Tranquillity 83 - 21 Piero Piccioni 84 - 12 Bloodywood 85 - 11 Frank Sinatra 86 - 10 Dream Theater&lt;/code&gt;&lt;/pre&gt; 87 - &lt;p&gt;It is true, I love Sam Cooke.&lt;/p&gt; 88 - &lt;p&gt;pdsfs allows mounting multiple repositories at a time. Allow me to 89 - introduce my friends:&lt;/p&gt; 90 - &lt;pre&gt;&lt;code&gt;λ pdsfs icyphox.sh anil.recoil.org steveklabnik.com tangled.sh 91 - using cached CAR file for...did:plc:hwevmowznbiukdf6uk5dwrrq 92 - using cached CAR file for...did:plc:nhyitepp3u4u6fcfboegzcjw 93 - download complete for...did:plc:3danwc67lo7obz2fmdg6jxcr 94 - download complete for...did:plc:wshs7t2adsemcrrd4snkeqli 95 - mounted at &amp;quot;mnt&amp;quot; 96 - hit enter to unmount and exit... 97 - 98 - # -- in a separate shell -- 99 - 100 - λ cat ./mnt/*/app.bsky.actor.profile/* \ 101 - | jq -r &amp;#39;&amp;quot;\(.displayName)\n\(.description)\n---&amp;quot;&amp;#39; \ 102 - | sed &amp;#39;/^$/d&amp;#39; 103 - Steve Klabnik 104 - #rustlang, #jj-vcs, atproto, shitposts, urbanism. I 105 - contain multitudes. Working on #ruelang but just for 106 - fun. Currently in Austin, TX, but from Pittsburgh. 107 - Previously in Bushwick, the Mission, LA. 108 - --- 109 - Anirudh Oppiliappan 110 - building @tangled.sh — code collaboration platform built 111 - on atproto helsinki, finland · https://anirudh.fi · 112 - (somewhat) effective altruist 113 - --- 114 - Anil Madhavapeddy 115 - Professor of Planetary Computing at the University of 116 - Cambridge @cst.cam.ac.uk, where I co-lead the 117 - @eeg.cl.cam.ac.uk, and am also to found at 118 - @conservation.cam.ac.uk. Homepage at 119 - https://anil.recoil.org 120 - --- 121 - Tangled 122 - https://tangled.sh is a git collaboration platform built 123 - on atproto. Social coding, but for real this time! 124 - Discord: chat.tangled.sh IRC: #tangled @ libera.chat 125 - Built by @oppi.li &amp;amp; @icyphox.sh 126 - ---&lt;/code&gt;&lt;/pre&gt; 127 - &lt;p&gt;All my friends use &lt;a href="https://tangled.sh"&gt;tangled.sh&lt;/a&gt;, which 128 - requires them to publish their ssh public key to their PDii (PDSes?). 129 - Perhaps I would like to add their keys to my allowed_signers file to 130 - verify their commit signatures:&lt;/p&gt; 131 - &lt;pre&gt;&lt;code&gt;λ for dir in ./*/sh.tangled.publicKey; 132 - do cat $dir/$(ls -r $dir | head -n1) | jq -r &amp;#39;.key&amp;#39;; 133 - done | tee allowed_signers 134 - ssh-rsa AAAAB3NzaC1yc2EAAA...dHPqc= steveklabnik@DESKTOP-VV370NK 135 - ssh-ed25519 AAAAC3NzaC1lZD...g9bAdk icy@wyndle 136 - ssh-ed25519 AAAAC3NzaC1lZD...BqlM1u anil@recoil.org&lt;/code&gt;&lt;/pre&gt; 137 - &lt;p&gt;FUSE is quite liberating in that it allows you to represent anything 138 - as a filesystem. When applications like &lt;code&gt;ls&lt;/code&gt; and 139 - &lt;code&gt;cat&lt;/code&gt; are executed, the syscalls to &lt;code&gt;open&lt;/code&gt; and 140 - &lt;code&gt;read&lt;/code&gt; are rerouted to your custom fs implementation (pdsfs 141 - in this case). The custom fs implementation is free to as it pleases, in 142 - fact the first iteration of pdsfs accessed the network for each 143 - open/read call to fetch live data from PDii.&lt;/p&gt;</description> 144 - <link>https://oppi.li/posts/mounting_the_atmosphere/</link> 145 - <pubDate>Thu, 31 Jul 2025 07:56:00 +0000</pubDate> 146 - <guid>https://oppi.li/posts/mounting_the_atmosphere/</guid> 147 - </item> 148 - <item> 149 - <title>Configuring Jujutsu</title> 150 - <description>&lt;p&gt;There are a lot of reasons to use &lt;a 151 - href="https://github.com/jj-vcs/jj"&gt;jujutsu&lt;/a&gt;, but this post is not 152 - about that. I have this terrible habit of turning every knob on a tool 153 - before I even fully absorb how it works; and boy does &lt;code&gt;jj&lt;/code&gt; 154 - have a lot of knobs.&lt;/p&gt; 155 - &lt;h3 id="templates"&gt;Templates&lt;/h3&gt; 156 - &lt;p&gt;&lt;code&gt;jj&lt;/code&gt; let you tweak nearly every single character of its 157 - output. In fact, &lt;code&gt;jj&lt;/code&gt; includes a full-blown templating 158 - language to do so. Lets start from the basics:&lt;/p&gt; 159 - &lt;p&gt;Format all timestamps in relative fashion, like “5 hours ago”:&lt;/p&gt; 160 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre 161 - class="sourceCode toml"&gt;&lt;code class="sourceCode toml"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;[template-aliases]&lt;/span&gt;&lt;/span&gt; 162 - &lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;&amp;quot;format_timestamp(timestamp)&amp;quot;&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;timestamp.ago()&amp;quot;&lt;/span&gt;&lt;span class="er"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 163 - &lt;p&gt;The default nodes in the log graph are a bit large for my taste, we 164 - can modify the &lt;code&gt;log_node&lt;/code&gt; template to fix that:&lt;/p&gt; 165 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 166 - class="sourceCode toml"&gt;&lt;code class="sourceCode toml"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;[templates]&lt;/span&gt;&lt;/span&gt; 167 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;log_node&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt; 168 - &lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; label(&amp;quot;node&amp;quot;,&lt;/span&gt;&lt;/span&gt; 169 - &lt;span id="cb2-4"&gt;&lt;a href="#cb2-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; coalesce(&lt;/span&gt;&lt;/span&gt; 170 - &lt;span id="cb2-5"&gt;&lt;a href="#cb2-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; if(!self, label(&amp;quot;elided&amp;quot;, &amp;quot;~&amp;quot;)),&lt;/span&gt;&lt;/span&gt; 171 - &lt;span id="cb2-6"&gt;&lt;a href="#cb2-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; if(current_working_copy, label(&amp;quot;working_copy&amp;quot;, &amp;quot;@&amp;quot;)),&lt;/span&gt;&lt;/span&gt; 172 - &lt;span id="cb2-7"&gt;&lt;a href="#cb2-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; if(conflict, label(&amp;quot;conflict&amp;quot;, &amp;quot;×&amp;quot;)),&lt;/span&gt;&lt;/span&gt; 173 - &lt;span id="cb2-8"&gt;&lt;a href="#cb2-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; if(immutable, label(&amp;quot;immutable&amp;quot;, &amp;quot;*&amp;quot;)),&lt;/span&gt;&lt;/span&gt; 174 - &lt;span id="cb2-9"&gt;&lt;a href="#cb2-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; label(&amp;quot;normal&amp;quot;, &amp;quot;·&amp;quot;)&lt;/span&gt;&lt;/span&gt; 175 - &lt;span id="cb2-10"&gt;&lt;a href="#cb2-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; )&lt;/span&gt;&lt;/span&gt; 176 - &lt;span id="cb2-11"&gt;&lt;a href="#cb2-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; )&lt;/span&gt;&lt;/span&gt; 177 - &lt;span id="cb2-12"&gt;&lt;a href="#cb2-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 178 - &lt;p&gt;This uses smaller iconography in &lt;code&gt;jj log&lt;/code&gt; (these icons are 179 - also more widely available in fonts, in my experience):&lt;/p&gt; 180 - &lt;pre&gt;&lt;code&gt;@ wuuownsw me@oppi.li 21 minutes ago 30d1bd12 181 - │ (no description set) 182 - · plmznxvy me@oppi.li 5 hours ago push-plmznxvyqrqw git_head() 051c142e 183 - │ appview: pulls: bump sourceRev for stacks without causing resubmits 184 - │ * towvwqxk x@icyphox.sh 4 hours ago master a588f625 185 - │ │ appview: pages/markup: don&amp;#39;t double camo in post process 186 - │ * ryzqnyvs me@oppi.li 4 hours ago ea4b520a 187 - │ │ appview: fix stack merging 188 - │ * kqvutzxr me@oppi.li 4 hours ago ff73ca23 189 - ├─╯ appview: rework RepoLanguages 190 - * vwpqwmms jeynesbrook@gmail.com 8 hours ago d759587b 191 - │ appview: repo: inject language percentage into repo index 192 - ~&lt;/code&gt;&lt;/pre&gt; 193 - &lt;p&gt;Much nicer! And speaking of &lt;code&gt;log&lt;/code&gt;, I find it hard to parse 194 - the output when every change occupies two lines instead of just one 195 - line, we can remedy that quickly; in fact, &lt;code&gt;jj&lt;/code&gt; has a builtin 196 - template for single-line log outputs:&lt;/p&gt; 197 - &lt;pre&gt;&lt;code&gt;λ jj log -T builtin_log_oneline 198 - @ wuuownsw me@oppi.li 22 minutes ago 30d1bd12 (no description set) 199 - · plmznxvy me@oppi.li 5 hours ago push-plmznxvyqrqw git_head() 051c142e appview: pulls: bump sourceRev for stacks without causing resubmits 200 - │ * towvwqxk x@icyphox.sh 4 hours ago master a588f625 appview: pages/markup: don&amp;#39;t double camo in post process 201 - │ * ryzqnyvs me@oppi.li 4 hours ago ea4b520a appview: fix stack merging 202 - │ * kqvutzxr me@oppi.li 4 hours ago ff73ca23 appview: rework RepoLanguages 203 - ├─╯ 204 - * vwpqwmms jeynesbrook 8 hours ago d759587b appview: repo: inject language percentage into repo index 205 - 206 - ~&lt;/code&gt;&lt;/pre&gt; 207 - &lt;p&gt;Bit too long! I personally do not always need author and time 208 - information when quickly scrolling through the log, so I created my own 209 - template based on &lt;code&gt;builtin_log_oneline&lt;/code&gt;:&lt;/p&gt; 210 - &lt;div class="sourceCode" id="cb5"&gt;&lt;pre 211 - class="sourceCode toml"&gt;&lt;code class="sourceCode toml"&gt;&lt;span id="cb5-1"&gt;&lt;a href="#cb5-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;[templates]&lt;/span&gt;&lt;/span&gt; 212 - &lt;span id="cb5-2"&gt;&lt;a href="#cb5-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;log&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt; 213 - &lt;span id="cb5-3"&gt;&lt;a href="#cb5-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; if(root,&lt;/span&gt;&lt;/span&gt; 214 - &lt;span id="cb5-4"&gt;&lt;a href="#cb5-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; format_root_commit(self),&lt;/span&gt;&lt;/span&gt; 215 - &lt;span id="cb5-5"&gt;&lt;a href="#cb5-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; label(if(current_working_copy, &amp;quot;working_copy&amp;quot;),&lt;/span&gt;&lt;/span&gt; 216 - &lt;span id="cb5-6"&gt;&lt;a href="#cb5-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; concat(&lt;/span&gt;&lt;/span&gt; 217 - &lt;span id="cb5-7"&gt;&lt;a href="#cb5-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; separate(&amp;quot; &amp;quot;,&lt;/span&gt;&lt;/span&gt; 218 - &lt;span id="cb5-8"&gt;&lt;a href="#cb5-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; format_short_change_id_with_hidden_and_divergent_info(self),&lt;/span&gt;&lt;/span&gt; 219 - &lt;span id="cb5-9"&gt;&lt;a href="#cb5-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; if(empty, label(&amp;quot;empty&amp;quot;, &amp;quot;(empty)&amp;quot;)),&lt;/span&gt;&lt;/span&gt; 220 - &lt;span id="cb5-10"&gt;&lt;a href="#cb5-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; if(description,&lt;/span&gt;&lt;/span&gt; 221 - &lt;span id="cb5-11"&gt;&lt;a href="#cb5-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; description.first_line(),&lt;/span&gt;&lt;/span&gt; 222 - &lt;span id="cb5-12"&gt;&lt;a href="#cb5-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; label(if(empty, &amp;quot;empty&amp;quot;), description_placeholder),&lt;/span&gt;&lt;/span&gt; 223 - &lt;span id="cb5-13"&gt;&lt;a href="#cb5-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; ),&lt;/span&gt;&lt;/span&gt; 224 - &lt;span id="cb5-14"&gt;&lt;a href="#cb5-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; bookmarks,&lt;/span&gt;&lt;/span&gt; 225 - &lt;span id="cb5-15"&gt;&lt;a href="#cb5-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; tags,&lt;/span&gt;&lt;/span&gt; 226 - &lt;span id="cb5-16"&gt;&lt;a href="#cb5-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; working_copies,&lt;/span&gt;&lt;/span&gt; 227 - &lt;span id="cb5-17"&gt;&lt;a href="#cb5-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; if(git_head, label(&amp;quot;git_head&amp;quot;, &amp;quot;HEAD&amp;quot;)),&lt;/span&gt;&lt;/span&gt; 228 - &lt;span id="cb5-18"&gt;&lt;a href="#cb5-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; if(conflict, label(&amp;quot;conflict&amp;quot;, &amp;quot;conflict&amp;quot;)),&lt;/span&gt;&lt;/span&gt; 229 - &lt;span id="cb5-19"&gt;&lt;a href="#cb5-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; if(config(&amp;quot;ui.show-cryptographic-signatures&amp;quot;).as_boolean(),&lt;/span&gt;&lt;/span&gt; 230 - &lt;span id="cb5-20"&gt;&lt;a href="#cb5-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; format_short_cryptographic_signature(signature)),&lt;/span&gt;&lt;/span&gt; 231 - &lt;span id="cb5-21"&gt;&lt;a href="#cb5-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; ) ++ &amp;quot;\n&amp;quot;,&lt;/span&gt;&lt;/span&gt; 232 - &lt;span id="cb5-22"&gt;&lt;a href="#cb5-22" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; ),&lt;/span&gt;&lt;/span&gt; 233 - &lt;span id="cb5-23"&gt;&lt;a href="#cb5-23" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; )&lt;/span&gt;&lt;/span&gt; 234 - &lt;span id="cb5-24"&gt;&lt;a href="#cb5-24" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; )&lt;/span&gt;&lt;/span&gt; 235 - &lt;span id="cb5-25"&gt;&lt;a href="#cb5-25" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 236 - &lt;p&gt;Which produces:&lt;/p&gt; 237 - &lt;pre&gt;&lt;code&gt;@ wuuownsw (no description set) 238 - · plmznxvy appview: pulls: bump sourceRev for stacks without causing resubmits push-plmznxvyqrqw HEAD 239 - │ * towvwqxk appview: pages/markup: don&amp;#39;t double camo in post process master 240 - │ * ryzqnyvs appview: fix stack merging 241 - │ * kqvutzxr appview: rework RepoLanguages 242 - ├─╯ 243 - * vwpqwmms appview: repo: inject language percentage into repo index 244 - 245 - ~&lt;/code&gt;&lt;/pre&gt; 246 - &lt;p&gt;Sweet! To get a more detailed log, you can always use a different 247 - template for the output: 248 - &lt;code&gt;jj log -T builtin_log_detailed&lt;/code&gt;.&lt;/p&gt; 249 - &lt;p&gt;With git, I set &lt;code&gt;commit.verbose&lt;/code&gt; to true, this lets me 250 - view the diff when composing a commit messaege in my 251 - &lt;code&gt;$EDITOR&lt;/code&gt;:&lt;/p&gt; 252 - &lt;div class="sourceCode" id="cb7"&gt;&lt;pre 253 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb7-1"&gt;&lt;a href="#cb7-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; git commit&lt;/span&gt; 254 - &lt;span id="cb7-2"&gt;&lt;a href="#cb7-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# no message passed, opens $EDITOR to compose message&lt;/span&gt;&lt;/span&gt; 255 - &lt;span id="cb7-3"&gt;&lt;a href="#cb7-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 256 - &lt;span id="cb7-4"&gt;&lt;a href="#cb7-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# -- inside vim --&lt;/span&gt;&lt;/span&gt; 257 - &lt;span id="cb7-5"&gt;&lt;a href="#cb7-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 258 - &lt;span id="cb7-6"&gt;&lt;a href="#cb7-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Please enter the commit message for your changes. Lines starting&lt;/span&gt;&lt;/span&gt; 259 - &lt;span id="cb7-7"&gt;&lt;a href="#cb7-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# with &amp;#39;#&amp;#39; will be ignored, and an empty message aborts the commit.&lt;/span&gt;&lt;/span&gt; 260 - &lt;span id="cb7-8"&gt;&lt;a href="#cb7-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;#&lt;/span&gt;&lt;/span&gt; 261 - &lt;span id="cb7-9"&gt;&lt;a href="#cb7-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# On branch trunk&lt;/span&gt;&lt;/span&gt; 262 - &lt;span id="cb7-10"&gt;&lt;a href="#cb7-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Changes to be committed:&lt;/span&gt;&lt;/span&gt; 263 - &lt;span id="cb7-11"&gt;&lt;a href="#cb7-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# new file: scripts/handle.js&lt;/span&gt;&lt;/span&gt; 264 - &lt;span id="cb7-12"&gt;&lt;a href="#cb7-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;#&lt;/span&gt;&lt;/span&gt; 265 - &lt;span id="cb7-13"&gt;&lt;a href="#cb7-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# ------------------------ &amp;gt;8 ------------------------&lt;/span&gt;&lt;/span&gt; 266 - &lt;span id="cb7-14"&gt;&lt;a href="#cb7-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Do not modify or remove the line above.&lt;/span&gt;&lt;/span&gt; 267 - &lt;span id="cb7-15"&gt;&lt;a href="#cb7-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# Everything below it will be ignored.&lt;/span&gt;&lt;/span&gt; 268 - &lt;span id="cb7-16"&gt;&lt;a href="#cb7-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;diff&lt;/span&gt; &lt;span class="at"&gt;--git&lt;/span&gt; a/scripts/handle.js b/scripts/handle.js&lt;/span&gt; 269 - &lt;span id="cb7-17"&gt;&lt;a href="#cb7-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;new&lt;/span&gt; file mode 100644&lt;/span&gt; 270 - &lt;span id="cb7-18"&gt;&lt;a href="#cb7-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;index&lt;/span&gt; 0000000..d8a07f3&lt;/span&gt; 271 - &lt;span id="cb7-19"&gt;&lt;a href="#cb7-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;---&lt;/span&gt; /dev/null&lt;/span&gt; 272 - &lt;span id="cb7-20"&gt;&lt;a href="#cb7-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;+++&lt;/span&gt; b/scripts/handle.js&lt;/span&gt; 273 - &lt;span id="cb7-21"&gt;&lt;a href="#cb7-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;@@&lt;/span&gt; &lt;span class="at"&gt;-0,0&lt;/span&gt; +1,104 @@&lt;/span&gt; 274 - &lt;span id="cb7-22"&gt;&lt;a href="#cb7-22" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;+//&lt;/span&gt; Run using node handle.js&lt;/span&gt; 275 - &lt;span id="cb7-23"&gt;&lt;a href="#cb7-23" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;+//&lt;/span&gt; Install axios using npm install axios&lt;/span&gt; 276 - &lt;span id="cb7-24"&gt;&lt;a href="#cb7-24" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;+//&lt;/span&gt; nodejs v18+&lt;/span&gt; 277 - &lt;span id="cb7-25"&gt;&lt;a href="#cb7-25" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;+const&lt;/span&gt; axios = require&lt;span class="er"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;axios&amp;quot;&lt;/span&gt;&lt;span class="kw"&gt;);&lt;/span&gt;&lt;/span&gt; 278 - &lt;span id="cb7-26"&gt;&lt;a href="#cb7-26" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;.&lt;/span&gt;&lt;/span&gt; 279 - &lt;span id="cb7-27"&gt;&lt;a href="#cb7-27" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;.&lt;/span&gt;&lt;/span&gt; 280 - &lt;span id="cb7-28"&gt;&lt;a href="#cb7-28" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;.&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 281 - &lt;p&gt;The equivalent in &lt;code&gt;jj&lt;/code&gt; requires modifying the 282 - &lt;code&gt;draft_commit_description&lt;/code&gt; template:&lt;/p&gt; 283 - &lt;div class="sourceCode" id="cb8"&gt;&lt;pre 284 - class="sourceCode toml"&gt;&lt;code class="sourceCode toml"&gt;&lt;span id="cb8-1"&gt;&lt;a href="#cb8-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;[templates]&lt;/span&gt;&lt;/span&gt; 285 - &lt;span id="cb8-2"&gt;&lt;a href="#cb8-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;draft_commit_description&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt; 286 - &lt;span id="cb8-3"&gt;&lt;a href="#cb8-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; concat(&lt;/span&gt;&lt;/span&gt; 287 - &lt;span id="cb8-4"&gt;&lt;a href="#cb8-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; coalesce(description, default_commit_description, &amp;quot;\n&amp;quot;),&lt;/span&gt;&lt;/span&gt; 288 - &lt;span id="cb8-5"&gt;&lt;a href="#cb8-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; surround(&lt;/span&gt;&lt;/span&gt; 289 - &lt;span id="cb8-6"&gt;&lt;a href="#cb8-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; &amp;quot;\nJJ: This commit contains the following changes:\n&amp;quot;, &amp;quot;&amp;quot;,&lt;/span&gt;&lt;/span&gt; 290 - &lt;span id="cb8-7"&gt;&lt;a href="#cb8-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; indent(&amp;quot;JJ: &amp;quot;, diff.stat(72)),&lt;/span&gt;&lt;/span&gt; 291 - &lt;span id="cb8-8"&gt;&lt;a href="#cb8-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; ),&lt;/span&gt;&lt;/span&gt; 292 - &lt;span id="cb8-9"&gt;&lt;a href="#cb8-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; &amp;quot;\nJJ: ignore-rest\n&amp;quot;,&lt;/span&gt;&lt;/span&gt; 293 - &lt;span id="cb8-10"&gt;&lt;a href="#cb8-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; diff.git(),&lt;/span&gt;&lt;/span&gt; 294 - &lt;span id="cb8-11"&gt;&lt;a href="#cb8-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="vs"&gt; )&lt;/span&gt;&lt;/span&gt; 295 - &lt;span id="cb8-12"&gt;&lt;a href="#cb8-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt;&amp;#39;&amp;#39;&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 296 - &lt;h3 id="revsets"&gt;Revsets&lt;/h3&gt; 297 - &lt;p&gt;As a not-so-power-user, I presently only use revsets to improve my 298 - &lt;code&gt;jj log&lt;/code&gt; experience. In the context of &lt;code&gt;jj log&lt;/code&gt;, 299 - giving it a revset means “here is a bag of revisions, graph them for me 300 - neatly”.&lt;/p&gt; 301 - &lt;p&gt;I find myself using the “rebase” flow quite often:&lt;/p&gt; 302 - &lt;ul&gt; 303 - &lt;li&gt;I hack on a stack of changes&lt;/li&gt; 304 - &lt;li&gt;&lt;code&gt;trunk&lt;/code&gt; is updated by my fellow collaborators&lt;/li&gt; 305 - &lt;li&gt;I rebase my stack using 306 - &lt;code&gt;jj rebase -s &amp;lt;mine&amp;gt; -d &amp;lt;trunk&amp;gt;&lt;/code&gt;&lt;/li&gt; 307 - &lt;/ul&gt; 308 - &lt;p&gt;And I scrub through the log output to roughly figure out how much 309 - work it would be to rebase, what I need for this is:&lt;/p&gt; 310 - &lt;ul&gt; 311 - &lt;li&gt;the changes added to &lt;code&gt;trunk&lt;/code&gt; since I last diverged from 312 - it&lt;/li&gt; 313 - &lt;li&gt;the changes add to my stack since I last diverged from 314 - &lt;code&gt;trunk&lt;/code&gt;&lt;/li&gt; 315 - &lt;/ul&gt; 316 - &lt;pre&gt;&lt;code&gt; X--Y--Z my stack 317 - / 318 - F--A--B--C--D trunk&lt;/code&gt;&lt;/pre&gt; 319 - &lt;p&gt;If you want &lt;code&gt;jj log&lt;/code&gt; to print this (and by “this”, I mean, 320 - the graph above, exactly as presented), you have to supply it a 321 - &lt;code&gt;revset&lt;/code&gt; argument that grabs X, Y, Z, A, B, C, D and F. Some 322 - examples of revsets are:&lt;/p&gt; 323 - &lt;pre&gt;&lt;code&gt;@ # the rev marking the working copy 324 - x # the rev identified by shorthand `x` 325 - x | y # the set of two revs, [x, y] 326 - @ | trunk() # the set of two revs, [@, trunk()] 327 - .. # everything in this repository 328 - all() # also everything in this repository 329 - fork_point(@ | trunk()) # the rev where @ and trunk() forked off&lt;/code&gt;&lt;/pre&gt; 330 - &lt;p&gt;And the revset that captures “ahead-behind” style output is:&lt;/p&gt; 331 - &lt;pre&gt;&lt;code&gt;trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)&lt;/code&gt;&lt;/pre&gt; 332 - &lt;p&gt;Which includes:&lt;/p&gt; 333 - &lt;ul&gt; 334 - &lt;li&gt;&lt;code&gt;trunk()..@&lt;/code&gt;: the new changes that my collaborators have 335 - added&lt;/li&gt; 336 - &lt;li&gt;&lt;code&gt;@..trunk()&lt;/code&gt;: the new changes that I have added&lt;/li&gt; 337 - &lt;li&gt;&lt;code&gt;trunk()&lt;/code&gt;: the trunk rev itself&lt;/li&gt; 338 - &lt;li&gt;&lt;code&gt;@::&lt;/code&gt;: all descendants of &lt;code&gt;@&lt;/code&gt;&lt;/li&gt; 339 - &lt;li&gt;&lt;code&gt;fork_point(trunk() | @)&lt;/code&gt;: the rev from which the two 340 - streams of work diverged&lt;/li&gt; 341 - &lt;/ul&gt; 342 - &lt;p&gt;Rougly, when working on a stack, this is what I can see by supplying 343 - the above revset expression (simplified output):&lt;/p&gt; 344 - &lt;div class="sourceCode" id="cb12"&gt;&lt;pre 345 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb12-1"&gt;&lt;a href="#cb12-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; jj log &lt;span class="at"&gt;-r&lt;/span&gt; &lt;span class="st"&gt;&amp;#39;trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)&amp;#39;&lt;/span&gt;&lt;/span&gt; 346 - &lt;span id="cb12-2"&gt;&lt;a href="#cb12-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;@&lt;/span&gt; xdihgmke &lt;span class="op"&gt;&amp;lt;&lt;/span&gt;-- me&lt;/span&gt; 347 - &lt;span id="cb12-3"&gt;&lt;a href="#cb12-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; vurstull&lt;/span&gt; 348 - &lt;span id="cb12-4"&gt;&lt;a href="#cb12-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; plmznxvy&lt;/span&gt; 349 - &lt;span id="cb12-5"&gt;&lt;a href="#cb12-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; ihefghyy&lt;/span&gt; 350 - &lt;span id="cb12-6"&gt;&lt;a href="#cb12-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; &lt;span class="pp"&gt;*&lt;/span&gt; towvwqxk trunk &lt;span class="op"&gt;&amp;lt;&lt;/span&gt;-- trunk&lt;/span&gt; 351 - &lt;span id="cb12-7"&gt;&lt;a href="#cb12-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; &lt;span class="pp"&gt;*&lt;/span&gt; ryzqnyvs&lt;/span&gt; 352 - &lt;span id="cb12-8"&gt;&lt;a href="#cb12-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; &lt;span class="pp"&gt;*&lt;/span&gt; kqvutzxr&lt;/span&gt; 353 - &lt;span id="cb12-9"&gt;&lt;a href="#cb12-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;├─╯&lt;/span&gt;&lt;/span&gt; 354 - &lt;span id="cb12-10"&gt;&lt;a href="#cb12-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;*&lt;/span&gt; vwpqwmms &lt;span class="op"&gt;&amp;lt;&lt;/span&gt;-- fork-point&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 355 - &lt;p&gt;And sometimes, when I am editing older parts of my stack 356 - (&lt;code&gt;@::&lt;/code&gt; helps us grab &lt;code&gt;xdihgmke&lt;/code&gt; and 357 - &lt;code&gt;vurstull&lt;/code&gt;):&lt;/p&gt; 358 - &lt;div class="sourceCode" id="cb13"&gt;&lt;pre 359 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb13-1"&gt;&lt;a href="#cb13-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; jj log &lt;span class="at"&gt;-r&lt;/span&gt; &lt;span class="st"&gt;&amp;#39;trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)&amp;#39;&lt;/span&gt;&lt;/span&gt; 360 - &lt;span id="cb13-2"&gt;&lt;a href="#cb13-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; xdihgmke&lt;/span&gt; 361 - &lt;span id="cb13-3"&gt;&lt;a href="#cb13-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; vurstull&lt;/span&gt; 362 - &lt;span id="cb13-4"&gt;&lt;a href="#cb13-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;@&lt;/span&gt; plmznxvy &lt;span class="op"&gt;&amp;lt;&lt;/span&gt;-- me&lt;/span&gt; 363 - &lt;span id="cb13-5"&gt;&lt;a href="#cb13-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; ihefghyy&lt;/span&gt; 364 - &lt;span id="cb13-6"&gt;&lt;a href="#cb13-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; &lt;span class="pp"&gt;*&lt;/span&gt; towvwqxk trunk &lt;span class="op"&gt;&amp;lt;&lt;/span&gt;-- trunk&lt;/span&gt; 365 - &lt;span id="cb13-7"&gt;&lt;a href="#cb13-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; &lt;span class="pp"&gt;*&lt;/span&gt; ryzqnyvs&lt;/span&gt; 366 - &lt;span id="cb13-8"&gt;&lt;a href="#cb13-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; &lt;span class="pp"&gt;*&lt;/span&gt; kqvutzxr&lt;/span&gt; 367 - &lt;span id="cb13-9"&gt;&lt;a href="#cb13-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;├─╯&lt;/span&gt;&lt;/span&gt; 368 - &lt;span id="cb13-10"&gt;&lt;a href="#cb13-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;*&lt;/span&gt; vwpqwmms &lt;span class="op"&gt;&amp;lt;&lt;/span&gt;-- fork-point&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 369 - &lt;p&gt;To rebase, I would run:&lt;/p&gt; 370 - &lt;pre&gt;&lt;code&gt;jj rebase -s ihefghyy -d towvwqxk&lt;/code&gt;&lt;/pre&gt; 371 - &lt;h3 id="aliases"&gt;Aliases&lt;/h3&gt; 372 - &lt;p&gt;I imagine that most git power users are already familiar with 373 - aliases. The only alias I see myself using often is:&lt;/p&gt; 374 - &lt;div class="sourceCode" id="cb15"&gt;&lt;pre 375 - class="sourceCode toml"&gt;&lt;code class="sourceCode toml"&gt;&lt;span id="cb15-1"&gt;&lt;a href="#cb15-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;[aliases]&lt;/span&gt;&lt;/span&gt; 376 - &lt;span id="cb15-2"&gt;&lt;a href="#cb15-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;tug&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;bookmark&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;move&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;--from&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;heads(::@- &amp;amp; bookmarks())&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;--to&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;@-&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;]&lt;/span&gt;&lt;span class="er"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 377 - &lt;p&gt;In action:&lt;/p&gt; 378 - &lt;div class="sourceCode" id="cb16"&gt;&lt;pre 379 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb16-1"&gt;&lt;a href="#cb16-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# ugh my bookmark is way behind&lt;/span&gt;&lt;/span&gt; 380 - &lt;span id="cb16-2"&gt;&lt;a href="#cb16-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; jj log&lt;/span&gt; 381 - &lt;span id="cb16-3"&gt;&lt;a href="#cb16-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;@&lt;/span&gt; xdihgmke&lt;/span&gt; 382 - &lt;span id="cb16-4"&gt;&lt;a href="#cb16-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; vurstull&lt;/span&gt; 383 - &lt;span id="cb16-5"&gt;&lt;a href="#cb16-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; cyzmakil&lt;/span&gt; 384 - &lt;span id="cb16-6"&gt;&lt;a href="#cb16-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; plmznxvy&lt;/span&gt; 385 - &lt;span id="cb16-7"&gt;&lt;a href="#cb16-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; ihefghyy some-bookmark&lt;/span&gt; 386 - &lt;span id="cb16-8"&gt;&lt;a href="#cb16-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt;&lt;/span&gt; 387 - &lt;span id="cb16-9"&gt;&lt;a href="#cb16-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~&lt;/span&gt;&lt;/span&gt; 388 - &lt;span id="cb16-10"&gt;&lt;a href="#cb16-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 389 - &lt;span id="cb16-11"&gt;&lt;a href="#cb16-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; jj tug&lt;/span&gt; 390 - &lt;span id="cb16-12"&gt;&lt;a href="#cb16-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 391 - &lt;span id="cb16-13"&gt;&lt;a href="#cb16-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# ready to push!&lt;/span&gt;&lt;/span&gt; 392 - &lt;span id="cb16-14"&gt;&lt;a href="#cb16-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; jj log&lt;/span&gt; 393 - &lt;span id="cb16-15"&gt;&lt;a href="#cb16-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;@&lt;/span&gt; xdihgmke&lt;/span&gt; 394 - &lt;span id="cb16-16"&gt;&lt;a href="#cb16-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; vurstull some-bookmark&lt;span class="pp"&gt;*&lt;/span&gt;&lt;/span&gt; 395 - &lt;span id="cb16-17"&gt;&lt;a href="#cb16-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; cyzmakil&lt;/span&gt; 396 - &lt;span id="cb16-18"&gt;&lt;a href="#cb16-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; plmznxvy&lt;/span&gt; 397 - &lt;span id="cb16-19"&gt;&lt;a href="#cb16-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;·&lt;/span&gt; ihefghyy&lt;/span&gt; 398 - &lt;span id="cb16-20"&gt;&lt;a href="#cb16-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt;&lt;/span&gt; 399 - &lt;span id="cb16-21"&gt;&lt;a href="#cb16-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 400 - &lt;p&gt;This alias was stolen from this &lt;a 401 - href="https://gist.github.com/thoughtpolice/8f2fd36ae17cd11b8e7bd93a70e31ad6#file-jjconfig-toml"&gt;wonderful 402 - gist&lt;/a&gt; by &lt;a 403 - href="https://github.com/jj-vcs/jj/blob/main/docs/testimonials.md?plain=1#L120"&gt;Austin 404 - Seipp&lt;/a&gt;.&lt;/p&gt; 405 - &lt;h3 id="experimental-features"&gt;Experimental features&lt;/h3&gt; 406 - &lt;p&gt;I use one experimental feature, that is only available on some of the 407 - newer versions of jujutsu:&lt;/p&gt; 408 - &lt;div class="sourceCode" id="cb17"&gt;&lt;pre 409 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb17-1"&gt;&lt;a href="#cb17-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# built from master&lt;/span&gt;&lt;/span&gt; 410 - &lt;span id="cb17-2"&gt;&lt;a href="#cb17-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; jj version&lt;/span&gt; 411 - &lt;span id="cb17-3"&gt;&lt;a href="#cb17-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;jj&lt;/span&gt; 0.29.0-8c7ca30074767257d75e3842581b61e764d022cf&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 412 - &lt;div class="sourceCode" id="cb18"&gt;&lt;pre 413 - class="sourceCode toml"&gt;&lt;code class="sourceCode toml"&gt;&lt;span id="cb18-1"&gt;&lt;a href="#cb18-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;[git]&lt;/span&gt;&lt;/span&gt; 414 - &lt;span id="cb18-2"&gt;&lt;a href="#cb18-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;write-change-id-header&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="cn"&gt;true&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 415 - &lt;p&gt;This writes an extra commit-header (not to be confused with 416 - commit-trailer, which goes directly in the commit body) with the jujutsu 417 - change-id, as seen by inspecting the commit object:&lt;/p&gt; 418 - &lt;div class="sourceCode" id="cb19"&gt;&lt;pre 419 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb19-1"&gt;&lt;a href="#cb19-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; git cat-file commit 4edebe96159bf81c3be662d0a2b4c7b08a062968&lt;/span&gt; 420 - &lt;span id="cb19-2"&gt;&lt;a href="#cb19-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;tree&lt;/span&gt; a9b7e9e3eed8a22c35829bf586d7560ec8396124&lt;/span&gt; 421 - &lt;span id="cb19-3"&gt;&lt;a href="#cb19-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;parent&lt;/span&gt; edc0d2750d4848bc05cfd3255ee1ca916bea9156&lt;/span&gt; 422 - &lt;span id="cb19-4"&gt;&lt;a href="#cb19-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;author&lt;/span&gt; oppiliappan &lt;span class="op"&gt;&amp;lt;&lt;/span&gt;me@oppi.li&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; 1747919102 +0100&lt;/span&gt; 423 - &lt;span id="cb19-5"&gt;&lt;a href="#cb19-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;committer&lt;/span&gt; oppiliappan &lt;span class="op"&gt;&amp;lt;&lt;/span&gt;me@oppi.li&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; 1747919102 +0100&lt;/span&gt; 424 - &lt;span id="cb19-6"&gt;&lt;a href="#cb19-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;change-id&lt;/span&gt; skrrxvvxlpzrqzpxlxksvryrykpxkvon &lt;span class="op"&gt;&amp;lt;&lt;/span&gt;-- this&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 425 - &lt;p&gt;This allows code forges to extract change-ids from commits, and most 426 - crucially: &lt;strong&gt;allows tracking changes across force pushes&lt;/strong&gt;. 427 - This enables the &lt;a 428 - href="https://gist.github.com/thoughtpolice/9c45287550a56b2047c6311fbadebed2"&gt;interdiff 429 - format&lt;/a&gt; of code-review. Stacking, commit-wise-review, and interdiff 430 - are all supported on &lt;a href="https://tangled.sh"&gt;tangled&lt;/a&gt; (you can 431 - read more &lt;a 432 - href="https://bsky.app/profile/tangled.sh/post/3lptwcb47kc2u"&gt;here&lt;/a&gt;), 433 - if you submit a branch with this experimental feature enabled.&lt;/p&gt; 434 - &lt;h3 id="misc"&gt;Misc&lt;/h3&gt; 435 - &lt;p&gt;Couple of other quality of life additions:&lt;/p&gt; 436 - &lt;div class="sourceCode" id="cb20"&gt;&lt;pre 437 - class="sourceCode toml"&gt;&lt;code class="sourceCode toml"&gt;&lt;span id="cb20-1"&gt;&lt;a href="#cb20-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;[ui]&lt;/span&gt;&lt;/span&gt; 438 - &lt;span id="cb20-2"&gt;&lt;a href="#cb20-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;default-command&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;status&amp;quot;&lt;/span&gt;&lt;/span&gt; 439 - &lt;span id="cb20-3"&gt;&lt;a href="#cb20-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;pager&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;delta&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 440 - &lt;p&gt;I also use nix and home-manager to configure jj, you can find my 441 - configuration &lt;a href="https://plonk.li/r/YQ"&gt;here&lt;/a&gt;.&lt;/p&gt;</description> 442 - <link>https://oppi.li/posts/configuring_jujutsu/</link> 443 - <pubDate>Sat, 24 May 2025 09:14:00 +0000</pubDate> 444 - <guid>https://oppi.li/posts/configuring_jujutsu/</guid> 445 - </item> 446 - <item> 447 - <title>Tales From Mainframe Modernization</title> 448 - <description>&lt;p&gt;At my last workplace, I wrote transpilers (or just &lt;a 449 - href="https://people.csail.mit.edu/rachit/post/transpiler/"&gt;compilers&lt;/a&gt; 450 - if you prefer) from mainframe languages (COBOL, JCL, BASIC etc.) to Java 451 - (in Rust!).&lt;/p&gt; 452 - &lt;p&gt;Legacy code is full of surprises. In the roughly 200k lines of COBOL 453 - that I had the (dis)pleasure of working with, I saw some wonderful hacks 454 - to get around the limitations of the system. Mainframes are also chock 455 - full of history.&lt;/p&gt; 456 - &lt;h3 id="base-10-numerics"&gt;Base-10 numerics&lt;/h3&gt; 457 - &lt;p&gt;This is the first thing that stood out to me when I looked at COBOL 458 - code, a data-definition (the phrase for “variable”) in COBOL is declared 459 - like so:&lt;/p&gt; 460 - &lt;pre&gt;&lt;code&gt; ,-- name 461 - | ,- type 462 - __|___ __|_ 463 - 01 HEIGHT PIC 9(3). 464 - -- --- 465 - | | 466 - | `- picture clause (keyword) 467 - `- level number &lt;/code&gt;&lt;/pre&gt; 468 - &lt;p&gt;That statement declares a variable called &lt;code&gt;HEIGHT&lt;/code&gt; with 469 - type &lt;code&gt;9(3)&lt;/code&gt;, which is shorthand for &lt;code&gt;999&lt;/code&gt;, which 470 - indicates “3-digit number”. The possible values for this variable are 471 - &lt;code&gt;0&lt;/code&gt; to &lt;code&gt;999&lt;/code&gt;!&lt;/p&gt; 472 - &lt;h3 id="internationalisation"&gt;Internationalisation&lt;/h3&gt; 473 - &lt;p&gt;Below is another data-definition in COBOL, declaring 3 variables:&lt;/p&gt; 474 - &lt;pre class="cobol"&gt;&lt;code&gt;01 FOO-PERSON. 475 - 05 FOO-NAME PIC X(5). 476 - 05 FOO-HEIGHT PIC 9(3).&lt;/code&gt;&lt;/pre&gt; 477 - &lt;p&gt;What that means is:&lt;/p&gt; 478 - &lt;ul&gt; 479 - &lt;li&gt;&lt;code&gt;FOO-PERSON&lt;/code&gt;: a “group” variable consisting of two other 480 - variables&lt;/li&gt; 481 - &lt;li&gt;&lt;code&gt;FOO-NAME&lt;/code&gt;: an alphanumeric type with 5 characters&lt;/li&gt; 482 - &lt;li&gt;&lt;code&gt;FOO-HEIGHT&lt;/code&gt;: a numeric type with 3 digits (remember, 483 - base 10 and not base 2)&lt;/li&gt; 484 - &lt;/ul&gt; 485 - &lt;p&gt;COBOL has an interesting construct called “REDEFINES”:&lt;/p&gt; 486 - &lt;pre class="cobol"&gt;&lt;code&gt;01 FOO-PERSON. 487 - 05 FOO-NAME PIC X(5). 488 - 05 FOO-HEIGHT PIC 9(3). 489 - 490 - 01 FOO-PERSONNE REDEFINES FOO-PERSON. 491 - 05 FOO-NOM PIC X(5). 492 - 05 FOO-TAILLE PIC 9(3).&lt;/code&gt;&lt;/pre&gt; 493 - &lt;p&gt;&lt;code&gt;FOO-PERSON&lt;/code&gt; and &lt;code&gt;FOO-PERSONNE&lt;/code&gt; refer to the 494 - same region of memory.&lt;/p&gt; 495 - &lt;p&gt;I helped modernise a codebase that had clearly been worked on by a 496 - Spanish consultancy at some point, and they had decided to redefine all 497 - data definitions in Spanish.&lt;/p&gt; 498 - &lt;h3 id="string-parsing"&gt;String parsing&lt;/h3&gt; 499 - &lt;p&gt;Here’s another fun one:&lt;/p&gt; 500 - &lt;pre class="cobol"&gt;&lt;code&gt; 01 FOO-PERSON. 501 - 05 FOO-NAME PIC X(5). 502 - 05 FOO-HEIGHT PIC 9(3). 503 - . 504 - . 505 - . 506 - 507 - MOVE &amp;quot;PETER&amp;quot; TO FOO-NAME. 508 - MOVE 175 TO FOO-HEIGHT. 509 - 510 - *&amp;gt; display the entire memory region 511 - DISPLAY FOO-PERSON. 512 - *&amp;gt; PETER175 513 - 514 - *&amp;gt; subscripting the first 7 bytes... 515 - DISPLAY FOO-PERSON (1:7) 516 - *&amp;gt; PETER17&lt;/code&gt;&lt;/pre&gt; 517 - &lt;p&gt;So data-definitions simply describe names for regions. Which enables 518 - a clever way to parse strings:&lt;/p&gt; 519 - &lt;pre class="cobol"&gt;&lt;code&gt; 01 DATE. 520 - 05 DD PIC 9(2). 521 - 05 FILLER PIC X. 522 - 05 MMM PIC A(3). 523 - 05 FILLER PIC X. 524 - 05 YYYY PIC 9(4). 525 - 526 - . 527 - . 528 - . 529 - 530 - MOVE &amp;quot;03 MAR 2025&amp;quot; TO DATE. 531 - DISPLAY &amp;quot;DAY: &amp;quot; DD. *&amp;gt; DAY: 03 532 - DISPLAY &amp;quot;MONTH: &amp;quot; MMM. *&amp;gt; MONTH: MAR 533 - DISPLAY &amp;quot;YEAR: &amp;quot; YYYY. *&amp;gt; YEAR: 2025 534 - 535 - *&amp;gt; also works: 536 - MOVE &amp;quot;03-MAR-2025&amp;quot; TO DATE.&lt;/code&gt;&lt;/pre&gt; 537 - &lt;h3 id="early-exit"&gt;Early exit&lt;/h3&gt; 538 - &lt;p&gt;I’d see this peppered around in a few places; which I later realized 539 - was a way to trigger an abnormal end to a batch job (possibly triggering 540 - an error handling routine in the outer job control system):&lt;/p&gt; 541 - &lt;pre class="cobol"&gt;&lt;code&gt; 01 CONSTANT-ZERO S9(9)V9 VALUE 0. 542 - 01 ABEND S9(9)V9. 543 - 544 - . 545 - . 546 - . 547 - 548 - COMPUTE ABEND = CONSTANT-ZERO / CONSTANT-ZERO.&lt;/code&gt;&lt;/pre&gt; 549 - &lt;h3 id="all-the-numbers"&gt;All the numbers&lt;/h3&gt; 550 - &lt;p&gt;I have yet to find an explanation for this one, but I once found a 551 - file with just the first 800 natural numbers defined as string 552 - constants:&lt;/p&gt; 553 - &lt;pre class="cobol"&gt;&lt;code&gt; 01 TC0001 X(5) &amp;quot;00001&amp;quot;. 554 - 01 TC0002 X(5) &amp;quot;00002&amp;quot;. 555 - 01 TC0003 X(5) &amp;quot;00003&amp;quot;. 556 - . 557 - . 558 - *&amp;gt; .... 800 lines later .... 559 - . 560 - . 561 - 01 TC0800 X(5) &amp;quot;00800&amp;quot;.&lt;/code&gt;&lt;/pre&gt; 562 - &lt;p&gt;The file was definitely not generated, and I can’t imagine text 563 - editors on the mainframe were all that advanced either.&lt;/p&gt; 564 - &lt;h3 id="dd---disk-destroyer"&gt;&lt;code&gt;dd&lt;/code&gt; - disk destroyer&lt;/h3&gt; 565 - &lt;p&gt;The &lt;code&gt;DD&lt;/code&gt; statement in the JCL subsystem stands for “data 566 - definition”, which is largely used to describe files and IO streams used 567 - by a batch job. The &lt;code&gt;dd&lt;/code&gt; command &lt;a href="#fn1" 568 - class="footnote-ref" id="fnref1" role="doc-noteref"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; on 569 - UNIX is named after this statement!&lt;/p&gt; 570 - &lt;section id="footnotes" class="footnotes footnotes-end-of-document" 571 - role="doc-endnotes"&gt; 572 - &lt;hr /&gt; 573 - &lt;ol&gt; 574 - &lt;li id="fn1"&gt;&lt;p&gt;&lt;a 575 - href="https://en.wikipedia.org/wiki/Dd_%28Unix%29#History"&gt;Wikipedia - 576 - dd (Unix)&lt;/a&gt;&lt;a href="#fnref1" class="footnote-back" 577 - role="doc-backlink"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt; 578 - &lt;/ol&gt; 579 - &lt;/section&gt;</description> 580 - <link>https://oppi.li/posts/tales_from_mainframe_modernization/</link> 581 - <pubDate>Wed, 21 May 2025 22:17:00 +0000</pubDate> 582 - <guid>https://oppi.li/posts/tales_from_mainframe_modernization/</guid> 583 - </item> 584 - <item> 585 - <title>OSC-52</title> 586 - <description>&lt;p&gt;I use &lt;code&gt;ssh&lt;/code&gt; a lot. Copying text from the remote machine to 587 - the host machine always sucked. But OSC-52 makes that easy.&lt;/p&gt; 588 - &lt;p&gt;OSC-52 is an ANSI escape sequence to write text to the terminal 589 - emulator. The terminal emulator, if it understands what is going on, 590 - will in turn write this text to the system clipboard.&lt;/p&gt; 591 - &lt;p&gt;What this means is some &lt;code&gt;printf&lt;/code&gt; magic can send text to 592 - your clipboard. I store this one-liner in a script called 593 - &lt;code&gt;oclip&lt;/code&gt;:&lt;/p&gt; 594 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre 595 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;printf&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;\033]52;c;%s\007&amp;quot;&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="fu"&gt;base64&lt;/span&gt; &lt;span class="op"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="dv"&gt;0&lt;/span&gt;&lt;span class="va"&gt;)&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 596 - &lt;p&gt;and I run it with:&lt;/p&gt; 597 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 598 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;remote&lt;/span&gt; $ cat some_file.txt &lt;span class="kw"&gt;|&lt;/span&gt; &lt;span class="ex"&gt;oclip&lt;/span&gt;&lt;/span&gt; 599 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 600 - &lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# some_file.txt&amp;#39;s contents are now the host&amp;#39;s clipboard&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 601 - &lt;h3 id="the-catch"&gt;The catch&lt;/h3&gt; 602 - &lt;p&gt;Your terminal emulator must support OSC-52, &lt;code&gt;alacritty&lt;/code&gt; 603 - and &lt;code&gt;termux&lt;/code&gt; seem to support this out of the box. In 604 - &lt;code&gt;st&lt;/code&gt;, OSC-52 works with this change to 605 - &lt;code&gt;config.h&lt;/code&gt;:&lt;/p&gt; 606 - &lt;pre&gt;&lt;code&gt;int allowwindowops = 1;&lt;/code&gt;&lt;/pre&gt; 607 - &lt;p&gt;If you are using &lt;code&gt;tmux&lt;/code&gt;, you need to flip this switch 608 - on:&lt;/p&gt; 609 - &lt;pre&gt;&lt;code&gt;set -s set-clipboard on&lt;/code&gt;&lt;/pre&gt; 610 - &lt;p&gt;If you are inside &lt;code&gt;nvim&lt;/code&gt;, it may work as expected as long 611 - as &lt;code&gt;$SSH_TTY&lt;/code&gt; is set. I sometimes physically start a session, 612 - and &lt;code&gt;ssh&lt;/code&gt; into the same session later from another machine, 613 - and &lt;code&gt;$SSH_TTY&lt;/code&gt; remains unset, so I force OSC-52 in 614 - &lt;code&gt;nvim&lt;/code&gt; at all times (see &lt;a 615 - href="https://neovim.io/doc/user/provider.html#clipboard-osc52"&gt;nvimdoc&lt;/a&gt;):&lt;/p&gt; 616 - &lt;div class="sourceCode" id="cb5"&gt;&lt;pre 617 - class="sourceCode lua"&gt;&lt;code class="sourceCode lua"&gt;&lt;span id="cb5-1"&gt;&lt;a href="#cb5-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;vim&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="va"&gt;g&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="va"&gt;clipboard&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 618 - &lt;span id="cb5-2"&gt;&lt;a href="#cb5-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;name&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;#39;OSC 52&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 619 - &lt;span id="cb5-3"&gt;&lt;a href="#cb5-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;copy&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 620 - &lt;span id="cb5-4"&gt;&lt;a href="#cb5-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="fu"&gt;require&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;vim.ui.clipboard.osc52&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;).&lt;/span&gt;copy&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;),&lt;/span&gt;&lt;/span&gt; 621 - &lt;span id="cb5-5"&gt;&lt;a href="#cb5-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;*&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="fu"&gt;require&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;vim.ui.clipboard.osc52&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;).&lt;/span&gt;copy&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;*&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;),&lt;/span&gt;&lt;/span&gt; 622 - &lt;span id="cb5-6"&gt;&lt;a href="#cb5-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;},&lt;/span&gt;&lt;/span&gt; 623 - &lt;span id="cb5-7"&gt;&lt;a href="#cb5-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;paste&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 624 - &lt;span id="cb5-8"&gt;&lt;a href="#cb5-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="fu"&gt;require&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;vim.ui.clipboard.osc52&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;).&lt;/span&gt;paste&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;+&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;),&lt;/span&gt;&lt;/span&gt; 625 - &lt;span id="cb5-9"&gt;&lt;a href="#cb5-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;[&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;*&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;]&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="fu"&gt;require&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;vim.ui.clipboard.osc52&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;).&lt;/span&gt;paste&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;*&amp;#39;&lt;/span&gt;&lt;span class="op"&gt;),&lt;/span&gt;&lt;/span&gt; 626 - &lt;span id="cb5-10"&gt;&lt;a href="#cb5-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;},&lt;/span&gt;&lt;/span&gt; 627 - &lt;span id="cb5-11"&gt;&lt;a href="#cb5-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 628 - &lt;p&gt;If you are inside &lt;code&gt;nvim&lt;/code&gt; inside &lt;code&gt;tmux&lt;/code&gt; inside 629 - an &lt;code&gt;ssh&lt;/code&gt; session inside &lt;code&gt;st&lt;/code&gt;, you neeed all of the 630 - above tweaks. &lt;code&gt;nvim&lt;/code&gt; will pass the contents around to 631 - &lt;code&gt;tmux&lt;/code&gt;, which in turn will pass the contents to 632 - &lt;code&gt;st&lt;/code&gt;, which should pass it to your system clipboard.&lt;/p&gt;</description> 633 - <link>https://oppi.li/posts/OSC-52/</link> 634 - <pubDate>Wed, 27 Nov 2024 22:56:00 +0000</pubDate> 635 - <guid>https://oppi.li/posts/OSC-52/</guid> 636 - </item> 637 - <item> 638 - <title>Introducing Tablespoon</title> 639 - <description>&lt;p&gt;&lt;a href="https://git.peppe.rs/languages/tbsp"&gt;tbsp&lt;/a&gt; (tree-based 640 - source-processing language) is an awk-like language that operates on 641 - tree-sitter syntax trees. To motivate the need for such a program, we 642 - could begin by writing a markdown-to-html converter using 643 - &lt;code&gt;tbsp&lt;/code&gt; and &lt;a 644 - href="https://github.com/tree-sitter-grammars/tree-sitter-markdown"&gt;tree-sitter-md&lt;/a&gt;. 645 - We need some markdown to begin with:&lt;/p&gt; 646 - &lt;pre&gt;&lt;code&gt;# 1 heading 647 - 648 - content of first paragraph 649 - 650 - ## 1.1 heading 651 - 652 - content of nested paragraph&lt;/code&gt;&lt;/pre&gt; 653 - &lt;p&gt;For future reference, this markdown is parsed like so by 654 - tree-sitter-md (visualization generated by &lt;a 655 - href="https://git.peppe.rs/cli/tree-viz"&gt;tree-viz&lt;/a&gt;):&lt;/p&gt; 656 - &lt;pre&gt;&lt;code&gt;document 657 - | section 658 - | | atx_heading 659 - | | | atx_h1_marker &amp;quot;#&amp;quot; 660 - | | | heading_content inline &amp;quot;1 heading&amp;quot; 661 - | | paragraph 662 - | | | inline &amp;quot;content of first paragraph&amp;quot; 663 - | | section 664 - | | | atx_heading 665 - | | | | atx_h2_marker &amp;quot;##&amp;quot; 666 - | | | | heading_content inline &amp;quot;1.1 heading&amp;quot; 667 - | | | paragraph 668 - | | | | inline &amp;quot;content of nested paragraph&amp;quot;&lt;/code&gt;&lt;/pre&gt; 669 - &lt;p&gt;Onto the converter itself. Every &lt;code&gt;tbsp&lt;/code&gt; program is written 670 - as a collection of stanzas. Typically, we start with a stanza like 671 - so:&lt;/p&gt; 672 - &lt;pre&gt;&lt;code&gt;BEGIN { 673 - int depth = 0; 674 - 675 - print(&amp;quot;&amp;lt;html&amp;gt;\n&amp;quot;); 676 - print(&amp;quot;&amp;lt;body&amp;gt;\n&amp;quot;); 677 - }&lt;/code&gt;&lt;/pre&gt; 678 - &lt;p&gt;The stanza begins with a “pattern”, in this case, &lt;code&gt;BEGIN&lt;/code&gt;, 679 - and is followed a block of code. This block specifically, is executed 680 - right at the beginning, before traversing the parse tree. In this 681 - stanza, we set a “depth” variable to keep track of nesting of markdown 682 - headers, and begin our html document by printing the 683 - &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; and &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt; tags.&lt;/p&gt; 684 - &lt;p&gt;We can follow this stanza with an &lt;code&gt;END&lt;/code&gt; stanza, that is 685 - executed after the traversal:&lt;/p&gt; 686 - &lt;pre&gt;&lt;code&gt;END { 687 - print(&amp;quot;&amp;lt;/body&amp;gt;\n&amp;quot;); 688 - print(&amp;quot;&amp;lt;/html&amp;gt;\n&amp;quot;); 689 - }&lt;/code&gt;&lt;/pre&gt; 690 - &lt;p&gt;In this stanza, we close off the tags we opened at the start of the 691 - document. We can move onto the interesting bits of the conversion 692 - now:&lt;/p&gt; 693 - &lt;pre&gt;&lt;code&gt;enter section { 694 - depth += 1; 695 - } 696 - leave section { 697 - depth -= 1; 698 - }&lt;/code&gt;&lt;/pre&gt; 699 - &lt;p&gt;The above stanzas begin with &lt;code&gt;enter&lt;/code&gt; and 700 - &lt;code&gt;leave&lt;/code&gt; clauses, followed by the name of a tree-sitter node 701 - kind: &lt;code&gt;section&lt;/code&gt;. The &lt;code&gt;section&lt;/code&gt; identifier is 702 - visible in the tree-visualization above, it encompasses a 703 - markdown-section, and is created for every markdown header. To 704 - understand how &lt;code&gt;tbsp&lt;/code&gt; executes above stanzas:&lt;/p&gt; 705 - &lt;pre&gt;&lt;code&gt;document ... depth = 0 706 - | section &amp;lt;-------- enter section (1) ... depth = 1 707 - | | atx_heading 708 - | | | inline 709 - | | paragraph 710 - | | | inline 711 - | | section &amp;lt;----- enter section (2) ... depth = 2 712 - | | | atx_heading 713 - | | | | inline 714 - | | | paragraph 715 - | | | | inline 716 - | | | &amp;lt;----------- leave section (2) ... depth = 1 717 - | | &amp;lt;-------------- leave section (1) ... depth = 0 &lt;/code&gt;&lt;/pre&gt; 718 - &lt;p&gt;The following stanzas should be self-explanatory now:&lt;/p&gt; 719 - &lt;pre&gt;&lt;code&gt;enter atx_heading { 720 - print(&amp;quot;&amp;lt;h&amp;quot;); 721 - print(depth); 722 - print(&amp;quot;&amp;gt;&amp;quot;); 723 - } 724 - leave atx_heading { 725 - print(&amp;quot;&amp;lt;/h&amp;quot;); 726 - print(depth); 727 - print(&amp;quot;&amp;gt;\n&amp;quot;); 728 - } 729 - 730 - enter inline { 731 - print(text(node)); 732 - }&lt;/code&gt;&lt;/pre&gt; 733 - &lt;p&gt;But an explanation is included nonetheless:&lt;/p&gt; 734 - &lt;pre&gt;&lt;code&gt;document ... depth = 0 735 - | section &amp;lt;-------- enter section (1) ... depth = 1 736 - | | atx_heading &amp;lt;- enter atx_heading ... print &amp;quot;&amp;lt;h1&amp;gt;&amp;quot; 737 - | | | inline &amp;lt;--- enter inline ... print .. 738 - | | | &amp;lt;----------- leave atx_heading ... print &amp;quot;&amp;lt;/h1&amp;gt;&amp;quot; 739 - | | paragraph 740 - | | | inline &amp;lt;--- enter inline ... print .. 741 - | | section &amp;lt;----- enter section (2) ... depth = 2 742 - | | | atx_heading enter atx_heading ... print &amp;quot;&amp;lt;h2&amp;gt;&amp;quot; 743 - | | | | inline &amp;lt;- enter inline ... print .. 744 - | | | | &amp;lt;-------- leave atx_heading ... print &amp;quot;&amp;lt;/h2&amp;gt;&amp;quot; 745 - | | | paragraph 746 - | | | | inline &amp;lt;- enter inline ... print .. 747 - | | | &amp;lt;----------- leave section (2) ... depth = 1 748 - | | &amp;lt;-------------- leave section (1) ... depth = 0 &lt;/code&gt;&lt;/pre&gt; 749 - &lt;p&gt;The &lt;a 750 - href="https://git.peppe.rs/languages/tbsp/tree/examples"&gt;examples&lt;/a&gt; 751 - directory contains a complete markdown-to-html converter, along with a 752 - few other motivating examples.&lt;/p&gt; 753 - &lt;h3 id="usage"&gt;Usage&lt;/h3&gt; 754 - &lt;p&gt;The &lt;code&gt;tbsp&lt;/code&gt; evaluator is written in rust, use cargo to 755 - build and run:&lt;/p&gt; 756 - &lt;pre&gt;&lt;code&gt;cargo build --release 757 - ./target/release/tbsp --help&lt;/code&gt;&lt;/pre&gt; 758 - &lt;p&gt;&lt;code&gt;tbsp&lt;/code&gt; requires three inputs:&lt;/p&gt; 759 - &lt;ul&gt; 760 - &lt;li&gt;a &lt;code&gt;tbsp&lt;/code&gt; program, referred to as “program file”&lt;/li&gt; 761 - &lt;li&gt;a language&lt;/li&gt; 762 - &lt;li&gt;an input file or some input text at stdin&lt;/li&gt; 763 - &lt;/ul&gt; 764 - &lt;p&gt;You can run the interpreter like so (this program prints an overview 765 - of a rust file):&lt;/p&gt; 766 - &lt;pre&gt;&lt;code&gt;$ ./target/release/tbsp \ 767 - -f./examples/code-overview/overview.tbsp \ 768 - -l rust \ 769 - src/main.rs 770 - module 771 - └╴struct Cli 772 - └╴trait Cli 773 - └╴fn program 774 - └╴fn language 775 - └╴fn file 776 - └╴fn try_consume_stdin 777 - └╴fn main&lt;/code&gt;&lt;/pre&gt;</description> 778 - <link>https://oppi.li/posts/introducing_tablespoon/</link> 779 - <pubDate>Thu, 01 Aug 2024 19:18:00 +0000</pubDate> 780 - <guid>https://oppi.li/posts/introducing_tablespoon/</guid> 781 - </item> 782 - <item> 783 - <title>Snip Snap</title> 784 - <description>&lt;p&gt;I regularly switch between exactly two things while working, a 785 - “current” and an “alternate” item; a lot of tools I use seem to support 786 - this flow.&lt;/p&gt; 787 - &lt;h4 id="git"&gt;git&lt;/h4&gt; 788 - &lt;p&gt;Pass &lt;code&gt;-&lt;/code&gt; to &lt;code&gt;git-checkout&lt;/code&gt; to switch to the 789 - previously active branch:&lt;/p&gt; 790 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre 791 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; git branch&lt;/span&gt; 792 - &lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;*&lt;/span&gt; foo&lt;/span&gt; 793 - &lt;span id="cb1-3"&gt;&lt;a href="#cb1-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;bar&lt;/span&gt;&lt;/span&gt; 794 - &lt;span id="cb1-4"&gt;&lt;a href="#cb1-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 795 - &lt;span id="cb1-5"&gt;&lt;a href="#cb1-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; git checkout bar&lt;/span&gt; 796 - &lt;span id="cb1-6"&gt;&lt;a href="#cb1-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; git branch&lt;/span&gt; 797 - &lt;span id="cb1-7"&gt;&lt;a href="#cb1-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;foo&lt;/span&gt;&lt;/span&gt; 798 - &lt;span id="cb1-8"&gt;&lt;a href="#cb1-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;*&lt;/span&gt; bar&lt;/span&gt; 799 - &lt;span id="cb1-9"&gt;&lt;a href="#cb1-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 800 - &lt;span id="cb1-10"&gt;&lt;a href="#cb1-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; git checkout &lt;span class="at"&gt;-&lt;/span&gt;&lt;/span&gt; 801 - &lt;span id="cb1-11"&gt;&lt;a href="#cb1-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; git branch&lt;/span&gt; 802 - &lt;span id="cb1-12"&gt;&lt;a href="#cb1-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;*&lt;/span&gt; foo&lt;/span&gt; 803 - &lt;span id="cb1-13"&gt;&lt;a href="#cb1-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;bar&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 804 - &lt;h4 id="bash---cd"&gt;bash - cd&lt;/h4&gt; 805 - &lt;p&gt;This may not be exclusive to &lt;code&gt;bash&lt;/code&gt;:&lt;/p&gt; 806 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 807 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/foo&lt;/span&gt; $ cd ~/bar&lt;/span&gt; 808 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/bar&lt;/span&gt; $ cd &lt;span class="at"&gt;-&lt;/span&gt;&lt;/span&gt; 809 - &lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/foo&lt;/span&gt; $&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 810 - &lt;p&gt;This is especially handy in combination with my &lt;a 811 - href="../curing_a_case_of_git-UX/"&gt;git-worktree flow&lt;/a&gt;:&lt;/p&gt; 812 - &lt;div class="sourceCode" id="cb3"&gt;&lt;pre 813 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb3-1"&gt;&lt;a href="#cb3-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/main-branch&lt;/span&gt; $ gwj feature&lt;/span&gt; 814 - &lt;span id="cb3-2"&gt;&lt;a href="#cb3-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/feat-branch&lt;/span&gt; $ cd &lt;span class="at"&gt;-&lt;/span&gt;&lt;/span&gt; 815 - &lt;span id="cb3-3"&gt;&lt;a href="#cb3-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/main-branch&lt;/span&gt; $&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 816 - &lt;h4 id="bash---jobs"&gt;bash - jobs&lt;/h4&gt; 817 - &lt;p&gt;I often suspend multiple &lt;code&gt;vim&lt;/code&gt; sessions with 818 - &lt;code&gt;Ctrl-Z&lt;/code&gt;:&lt;/p&gt; 819 - &lt;div class="sourceCode" id="cb4"&gt;&lt;pre 820 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb4-1"&gt;&lt;a href="#cb4-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; jobs&lt;/span&gt; 821 - &lt;span id="cb4-2"&gt;&lt;a href="#cb4-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;[1]+&lt;/span&gt; Stopped vim transpiler/src/transform.rs&lt;/span&gt; 822 - &lt;span id="cb4-3"&gt;&lt;a href="#cb4-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;[2]-&lt;/span&gt; Stopped git commit &lt;span class="at"&gt;--verbose&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 823 - &lt;p&gt;In the above example: I suspended &lt;code&gt;vim&lt;/code&gt; when working on 824 - &lt;code&gt;transform.rs&lt;/code&gt;, and then began working on a commit by running 825 - &lt;code&gt;git commit&lt;/code&gt; without a message flag (lets you craft a message 826 - in &lt;code&gt;$EDITOR&lt;/code&gt;). To bring the current job to the foreground, 827 - you can use &lt;code&gt;fg&lt;/code&gt;:&lt;/p&gt; 828 - &lt;div class="sourceCode" id="cb5"&gt;&lt;pre 829 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb5-1"&gt;&lt;a href="#cb5-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; fg&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 830 - &lt;p&gt;With a job identifier:&lt;/p&gt; 831 - &lt;div class="sourceCode" id="cb6"&gt;&lt;pre 832 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb6-1"&gt;&lt;a href="#cb6-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; fg %2 &lt;span class="co"&gt;# resumes interactive git commit&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 833 - &lt;p&gt;Or switch to “last” job, or the second-most-recently-resumed job:&lt;/p&gt; 834 - &lt;div class="sourceCode" id="cb7"&gt;&lt;pre 835 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb7-1"&gt;&lt;a href="#cb7-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; fg %-&lt;/span&gt; 836 - &lt;span id="cb7-2"&gt;&lt;a href="#cb7-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; %- &lt;span class="co"&gt;# shorthand&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 837 - &lt;h4 id="vim"&gt;vim&lt;/h4&gt; 838 - &lt;p&gt;Switch to the last active buffer with &lt;code&gt;Ctrl+^&lt;/code&gt;. In 839 - command-mode, &lt;code&gt;#&lt;/code&gt; refers to the last active buffer, you can 840 - use this as an argument to a few commands:&lt;/p&gt; 841 - &lt;pre class="vimscript"&gt;&lt;code&gt;:b# &amp;quot; switch to alternate buffer (same as Ctrl+^) 842 - :vsp# &amp;quot; create a vertical split with the alternate buffer 843 - :read# &amp;quot; read contents of alternate buffer into current buffer 844 - :!wc # &amp;quot; pass file name of alternate buffer to the command `wc`&lt;/code&gt;&lt;/pre&gt; 845 - &lt;p&gt;See &lt;code&gt;:help c_#&lt;/code&gt; for more.&lt;/p&gt; 846 - &lt;h4 id="tmux"&gt;tmux&lt;/h4&gt; 847 - &lt;p&gt;Switch to the last active tmux session with 848 - &lt;code&gt;&amp;lt;prefix&amp;gt;+shift+L&lt;/code&gt;.&lt;/p&gt; 849 - &lt;h4 id="qutebrowser"&gt;qutebrowser&lt;/h4&gt; 850 - &lt;p&gt;Switch to the last active tab with &lt;code&gt;g$&lt;/code&gt;.&lt;/p&gt;</description> 851 - <link>https://oppi.li/posts/snip_snap/</link> 852 - <pubDate>Wed, 29 May 2024 17:48:00 +0000</pubDate> 853 - <guid>https://oppi.li/posts/snip_snap/</guid> 854 - </item> 855 - <item> 856 - <title>Plain Text Journaling</title> 857 - <description>&lt;p&gt;I cobbled together a journaling system with {neo,}vim, coreutils and 858 - &lt;a href="http://www.fresse.org/dateutils"&gt;dateutils&lt;/a&gt;. This system is 859 - loosely based on &lt;a href="https://www.rydercarroll.com/"&gt;Ryder 860 - Caroll’s&lt;/a&gt; Bullet Journal method.&lt;/p&gt; 861 - &lt;p&gt;&lt;a href="https://u.peppe.rs/SpF.png"&gt;&lt;img 862 - src="https://u.peppe.rs/SpF.png" /&gt;&lt;/a&gt;&lt;/p&gt; 863 - &lt;h3 id="the-format"&gt;The format&lt;/h3&gt; 864 - &lt;p&gt;The journal for a given year is a directory:&lt;/p&gt; 865 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre 866 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; ls journal/&lt;/span&gt; 867 - &lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;2022/&lt;/span&gt; 2023/&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 868 - &lt;p&gt;In each directory are 12 files, one for each month of the year, 869 - numbered like so:&lt;/p&gt; 870 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 871 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; ls journal/2023/&lt;/span&gt; 872 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;01&lt;/span&gt; 02 03 04 05 06 07 08 09 10 11 12&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 873 - &lt;p&gt;We can now begin writing stuff down:&lt;/p&gt; 874 - &lt;div class="sourceCode" id="cb3"&gt;&lt;pre 875 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb3-1"&gt;&lt;a href="#cb3-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; vim journal/2023/1&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 876 - &lt;p&gt;Every month must start with a calendar of course, fill that in 877 - with:&lt;/p&gt; 878 - &lt;pre class="vim"&gt;&lt;code&gt;:read !cal -m&lt;/code&gt;&lt;/pre&gt; 879 - &lt;p&gt;Your entry for January might look like this:&lt;/p&gt; 880 - &lt;div class="sourceCode" id="cb5"&gt;&lt;pre 881 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb5-1"&gt;&lt;a href="#cb5-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; cat journal/2023/01&lt;/span&gt; 882 - &lt;span id="cb5-2"&gt;&lt;a href="#cb5-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;January&lt;/span&gt; 2023&lt;/span&gt; 883 - &lt;span id="cb5-3"&gt;&lt;a href="#cb5-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;Mo&lt;/span&gt; Tu We Th Fr Sa Su&lt;/span&gt; 884 - &lt;span id="cb5-4"&gt;&lt;a href="#cb5-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;1&lt;/span&gt;&lt;/span&gt; 885 - &lt;span id="cb5-5"&gt;&lt;a href="#cb5-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;2&lt;/span&gt; 3 4 5 6 7 8&lt;/span&gt; 886 - &lt;span id="cb5-6"&gt;&lt;a href="#cb5-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;9&lt;/span&gt; 10 11 12 13 14 15&lt;/span&gt; 887 - &lt;span id="cb5-7"&gt;&lt;a href="#cb5-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;16&lt;/span&gt; 17 18 19 20 21 22&lt;/span&gt; 888 - &lt;span id="cb5-8"&gt;&lt;a href="#cb5-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;23&lt;/span&gt; 24 25 26 27 28 29&lt;/span&gt; 889 - &lt;span id="cb5-9"&gt;&lt;a href="#cb5-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;30&lt;/span&gt; 31&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 890 - &lt;p&gt;I prefer planning week by week, as opposed to creating a task-list 891 - every day, here’s what I have for the first couple of weeks:&lt;/p&gt; 892 - &lt;pre&gt;&lt;code&gt; January 2023 893 - Mo Tu We Th Fr Sa Su 894 - 1 895 - 2 3 4 5 6 7 8 896 - 9 10 11 12 13 14 15 897 - 16 17 18 19 20 21 22 898 - 23 24 25 26 27 28 29 899 - 30 31 900 - 901 - 902 - week 1 903 - 904 - done apply leaves 905 - done dload boarding pass 906 - moved reply to dan 907 - 908 - 909 - week 2 910 - 911 - todo reply to dan 912 - todo pack bags 913 - done travel insurance 914 - todo weigh luggage&lt;/code&gt;&lt;/pre&gt; 915 - &lt;p&gt;I start the week by writing a header and each item that week is 916 - placed on its own line. The items are prefixed with a &lt;code&gt;todo&lt;/code&gt; 917 - or a &lt;code&gt;done&lt;/code&gt; signifier.&lt;/p&gt; 918 - &lt;h3 id="form-over-function"&gt;Form over function&lt;/h3&gt; 919 - &lt;p&gt;Right off the bat, the signifiers look very noisy, Even more so once 920 - we start introducing variety (I use “event”, “note” and “moved”):&lt;/p&gt; 921 - &lt;pre&gt;&lt;code&gt;week 1 922 - 923 - todo apply leaves 924 - done dload boarding pass 925 - todo reply to dan 926 - event fr trip 927 - note weight 68.6&lt;/code&gt;&lt;/pre&gt; 928 - &lt;p&gt;We can clean this up with “abbreviations” 929 - (&lt;code&gt;:h abbreviations&lt;/code&gt;):&lt;/p&gt; 930 - &lt;pre class="vim"&gt;&lt;code&gt;:iabbrev todo · 931 - :iabbrev done ×&lt;/code&gt;&lt;/pre&gt; 932 - &lt;p&gt;Now, typing this:&lt;/p&gt; 933 - &lt;pre&gt;&lt;code&gt;todo apply leaves&lt;/code&gt;&lt;/pre&gt; 934 - &lt;p&gt;Automatically inserts:&lt;/p&gt; 935 - &lt;pre&gt;&lt;code&gt;· apply leaves&lt;/code&gt;&lt;/pre&gt; 936 - &lt;p&gt;You can use &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;o&lt;/code&gt; as well, but 937 - &lt;code&gt;×&lt;/code&gt; (U+00D7, MULTIPLICATION SIGN) and &lt;code&gt;·&lt;/code&gt; (U+00B7, 938 - MIDDLE DOT) are more … &lt;em&gt;gourmet&lt;/em&gt;.&lt;/p&gt; 939 - &lt;p&gt;The other signifiers I use are:&lt;/p&gt; 940 - &lt;ul&gt; 941 - &lt;li&gt;&lt;code&gt;-&lt;/code&gt; for note&lt;/li&gt; 942 - &lt;li&gt;&lt;code&gt;o&lt;/code&gt; for event&lt;/li&gt; 943 - &lt;li&gt;&lt;code&gt;&amp;gt;&lt;/code&gt; for moved.&lt;/li&gt; 944 - &lt;/ul&gt; 945 - &lt;p&gt;Nit #2 is the lack of order. We can employ vim to introduce grouping 946 - and sorting. Select the list of entries for this week:&lt;/p&gt; 947 - &lt;pre class="vim"&gt;&lt;code&gt;vip &amp;quot; line-wise select inner paragraph 948 - :&amp;#39;&amp;lt;,&amp;#39;&amp;gt;sort &amp;quot; the markers &amp;#39;&amp;lt; and &amp;#39;&amp;gt; are automatically inserted, 949 - &amp;quot; they mark the start and end of the selection&lt;/code&gt;&lt;/pre&gt; 950 - &lt;p&gt;We end up with:&lt;/p&gt; 951 - &lt;pre&gt;&lt;code&gt;week 1 952 - 953 - · apply leaves 954 - · reply to dan 955 - × dload boarding pass&lt;/code&gt;&lt;/pre&gt; 956 - &lt;p&gt;The lines are grouped by their signifiers, segregating todo items 957 - from completed items. Luckily, MIDDLE DOT is lesser than MULTIPLICATION 958 - SIGN, so todo items are placed at the top. The same goes for 959 - &lt;code&gt;o&lt;/code&gt; and &lt;code&gt;x&lt;/code&gt; symbols, either set of signifiers will 960 - result in the same sorting order.&lt;/p&gt; 961 - &lt;p&gt;We can shorten this select-paragraph-invoke-sort dance by setting the 962 - &lt;code&gt;formatprg&lt;/code&gt; variable:&lt;/p&gt; 963 - &lt;pre class="vim"&gt;&lt;code&gt;:set formatprg=sort\ -V&lt;/code&gt;&lt;/pre&gt; 964 - &lt;p&gt;Now, hitting &lt;code&gt;gqip&lt;/code&gt; should automatically group and sort 965 - the items for the week under the cursor, moving todo items to the top. 966 - Finding signifier glyphs that suit your sorting preference is a fun 967 - exercise.&lt;/p&gt; 968 - &lt;h3 id="syntax-highlighting"&gt;Syntax highlighting&lt;/h3&gt; 969 - &lt;p&gt;Adding color to items introduces another layer of visual distinction. 970 - In truth, I like to deck it out just because.&lt;/p&gt; 971 - &lt;p&gt;First, create a few syntax groups:&lt;/p&gt; 972 - &lt;pre class="vim"&gt;&lt;code&gt;:syntax match JournalAll /.*/ &amp;quot; captures the entire buffer 973 - :syntax match JournalDone /^×.*/ &amp;quot; lines containing &amp;#39;done&amp;#39; items: × 974 - :syntax match JournalTodo /^·.*/ &amp;quot; lines containing &amp;#39;todo&amp;#39; items: · 975 - :syntax match JournalEvent /^o.*/ &amp;quot; lines containing &amp;#39;event&amp;#39; items: o 976 - :syntax match JournalNote /^- .*/ &amp;quot; lines containing &amp;#39;note&amp;#39; items: - 977 - :syntax match JournalMoved /^&amp;gt;.*/ &amp;quot; lines containing &amp;#39;moved&amp;#39; items: &amp;gt;&lt;/code&gt;&lt;/pre&gt; 978 - &lt;p&gt;Add highlights to each group:&lt;/p&gt; 979 - &lt;pre class="vim"&gt;&lt;code&gt;:highlight JournalAll ctermfg=12 &amp;quot; bright black 980 - :highlight JournalDone ctermfg=12 &amp;quot; bright black 981 - :highlight JournalEvent ctermfg=6 &amp;quot; cyan 982 - :highlight JournalMoved ctermfg=5 &amp;quot; magenta 983 - :highlight JournalNote ctermfg=3 &amp;quot; yellow&lt;/code&gt;&lt;/pre&gt; 984 - &lt;p&gt;In my terminal, this is rendered like so:&lt;/p&gt; 985 - &lt;p&gt;&lt;a href="https://u.peppe.rs/Du6.png"&gt;&lt;img 986 - src="https://u.peppe.rs/Du6.png" /&gt;&lt;/a&gt;&lt;/p&gt; 987 - &lt;h3 id="habit-tracking"&gt;Habit tracking&lt;/h3&gt; 988 - &lt;p&gt;While this is not a part of my journaling system anymore, a few 989 - headers and an awk script is all it takes to track habits. My weekly 990 - entries would include a couple of habit headers like so:&lt;/p&gt; 991 - &lt;pre&gt;&lt;code&gt;week 1 -------------- 992 - 993 - × wake up on time 994 - × water the plants 995 - 996 - spend 7.5 7 10 997 - --------------------- 998 - 999 - 1000 - week 2 -------------- 1001 - 1002 - · make the bed 1003 - · go to bed 1004 - 1005 - spend 30 2.75 6 1006 - ---------------------&lt;/code&gt;&lt;/pre&gt; 1007 - &lt;p&gt;Here, under the &lt;code&gt;spend&lt;/code&gt; header in week 1, are a list of 1008 - expenditures accumulated over the week. The monthly spend is calculated 1009 - with this awk script:&lt;/p&gt; 1010 - &lt;div class="sourceCode" id="cb17"&gt;&lt;pre 1011 - class="sourceCode awk"&gt;&lt;code class="sourceCode awk"&gt;&lt;span id="cb17-1"&gt;&lt;a href="#cb17-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;BEGIN&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;spend&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="dv"&gt;0&lt;/span&gt;&lt;span class="op"&gt;;}&lt;/span&gt;&lt;/span&gt; 1012 - &lt;span id="cb17-2"&gt;&lt;a href="#cb17-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ot"&gt;/&lt;/span&gt;&lt;span class="ss"&gt;spend&lt;/span&gt;&lt;span class="ot"&gt;/&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;span class="cf"&gt;for&lt;/span&gt;&lt;span class="op"&gt;(&lt;/span&gt;i&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="dv"&gt;1&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;i&lt;span class="op"&gt;&amp;lt;=&lt;/span&gt;&lt;span class="dt"&gt;$NF&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;i&lt;span class="op"&gt;++)&lt;/span&gt; spend&lt;span class="op"&gt;+=&lt;/span&gt;&lt;span class="dt"&gt;$i&lt;/span&gt;&lt;span class="op"&gt;;}&lt;/span&gt;&lt;/span&gt; 1013 - &lt;span id="cb17-3"&gt;&lt;a href="#cb17-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;END&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="kw"&gt;printf&lt;/span&gt; spend &lt;span class="st"&gt;&amp;quot;eur&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1014 - &lt;p&gt;And invoked like so:&lt;/p&gt; 1015 - &lt;pre&gt;&lt;code&gt;λ awk -f spend.awk journal/2023/01 1016 - 63.25eur&lt;/code&gt;&lt;/pre&gt; 1017 - &lt;h3 id="reflection"&gt;Reflection&lt;/h3&gt; 1018 - &lt;p&gt;Journaling is not just about planning what is to come, but also 1019 - reflecting on what has passed. It would make sense to simultaneously 1020 - look at the past few weeks’ entries while making your current one. To 1021 - open multiple months of entries at the same time:&lt;/p&gt; 1022 - &lt;pre&gt;&lt;code&gt;λ vim -O journal/2023/0{1,2,3}&lt;/code&gt;&lt;/pre&gt; 1023 - &lt;p&gt;Opens 3 months, side-by-side, in vertical splits:&lt;/p&gt; 1024 - &lt;pre&gt;&lt;code&gt;JANUARY ------------ │ FEBRUARY ----------- │ MARCH -------------- 1025 - │ │ 1026 - Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su 1027 - 1 │ 1 2 3 4 5 │ 1 2 3 4 5 1028 - 2 3 4 5 6 7 8 │ 6 7 8 9 10 11 12 │ 6 7 8 9 10 11 12 1029 - 9 10 11 12 13 14 15 │ 13 14 15 16 17 18 19 │ 13 14 15 16 17 18 19 1030 - 16 17 18 19 20 21 22 │ 20 21 22 23 24 25 26 │ 20 21 22 23 24 25 26 1031 - 23 24 25 26 27 28 29 │ 27 28 │ 27 28 29 30 31 1032 - 30 31 │ │ 1033 - │ │ 1034 - │ │ 1035 - WEEK 1 ------------- │ WEEK 1 ------------- │ WEEK 1 ------------- 1036 - │ │ 1037 - &amp;gt; latex setup │ &amp;gt; forex │ - weight: 64 1038 - × make the bed │ × clean shoes │ &amp;gt; close sg-pr 1039 - × 03: dentist │ × buy clothes │ × facewash 1040 - × integrate tsg │ × draw │ × groceries 1041 - │ │ 1042 - │ │ 1043 - WEEK 2 ------------- │ WEEK 2 ------------- │ WEEK 2 ------------- 1044 - │ │ 1045 - × latex setup │ - viral fever │ &amp;gt; close sg-pr 1046 - × send invoice │ × forex │ × plan meet 1047 - × stack-graph pr │ × activate sim │ × sg storage 1048 - │ × bitlbee │&lt;/code&gt;&lt;/pre&gt; 1049 - &lt;h3 id="reducing-friction"&gt;Reducing friction&lt;/h3&gt; 1050 - &lt;p&gt;Journaling already requires a solid amount of discipline and 1051 - consistency. The added friction of typing 1052 - &lt;code&gt;vim journal/$CURRENT_YEAR/$CURRENT_MONTH&lt;/code&gt; each time is doing 1053 - no favors.&lt;/p&gt; 1054 - &lt;p&gt;To open the current month based on system time:&lt;/p&gt; 1055 - &lt;div class="sourceCode" id="cb21"&gt;&lt;pre 1056 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb21-1"&gt;&lt;a href="#cb21-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; vim &lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="fu"&gt;date&lt;/span&gt; +&lt;span class="st"&gt;&amp;quot;%Y/%m&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1057 - &lt;p&gt;To open all the months within a 2 month window of today, is a little 1058 - trickier. The command we wish to generate is (if today is 2023/12):&lt;/p&gt; 1059 - &lt;div class="sourceCode" id="cb22"&gt;&lt;pre 1060 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb22-1"&gt;&lt;a href="#cb22-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; vim &lt;span class="at"&gt;-O&lt;/span&gt; 2023/10 2023/11 2023/12 2024/01 2024/02&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1061 - &lt;p&gt;And that is where &lt;code&gt;dateseq&lt;/code&gt; from &lt;a 1062 - href="http://www.fresse.org/dateutils"&gt;dateutils&lt;/a&gt; comes in handy, for 1063 - example:&lt;/p&gt; 1064 - &lt;div class="sourceCode" id="cb23"&gt;&lt;pre 1065 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb23-1"&gt;&lt;a href="#cb23-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; dateseq 2012-02-01 2012-03-01&lt;/span&gt; 1066 - &lt;span id="cb23-2"&gt;&lt;a href="#cb23-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;2012-02-01&lt;/span&gt;&lt;/span&gt; 1067 - &lt;span id="cb23-3"&gt;&lt;a href="#cb23-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;2012-02-02&lt;/span&gt;&lt;/span&gt; 1068 - &lt;span id="cb23-4"&gt;&lt;a href="#cb23-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;2012-02-03&lt;/span&gt;&lt;/span&gt; 1069 - &lt;span id="cb23-5"&gt;&lt;a href="#cb23-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;...&lt;/span&gt;&lt;/span&gt; 1070 - &lt;span id="cb23-6"&gt;&lt;a href="#cb23-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;2012-02-28&lt;/span&gt;&lt;/span&gt; 1071 - &lt;span id="cb23-7"&gt;&lt;a href="#cb23-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;2012-02-29&lt;/span&gt;&lt;/span&gt; 1072 - &lt;span id="cb23-8"&gt;&lt;a href="#cb23-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;2012-03-01&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1073 - &lt;p&gt;This script opens all months within a 2 month window of today:&lt;/p&gt; 1074 - &lt;div class="sourceCode" id="cb24"&gt;&lt;pre 1075 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb24-1"&gt;&lt;a href="#cb24-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; vim &lt;span class="at"&gt;-O&lt;/span&gt; &lt;span class="va"&gt;$(&lt;/span&gt;&lt;/span&gt; 1076 - &lt;span id="cb24-2"&gt;&lt;a href="#cb24-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;dateseq&lt;/span&gt; &lt;span class="dt"&gt;\&lt;/span&gt;&lt;/span&gt; 1077 - &lt;span id="cb24-3"&gt;&lt;a href="#cb24-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="fu"&gt;date&lt;/span&gt; &lt;span class="at"&gt;--date&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;2 months ago&amp;quot;&lt;/span&gt; +%Y/%m&lt;span class="va"&gt;)&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="dt"&gt;\&lt;/span&gt;&lt;/span&gt; 1078 - &lt;span id="cb24-4"&gt;&lt;a href="#cb24-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="fu"&gt;date&lt;/span&gt; &lt;span class="at"&gt;--date&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;2 months&amp;quot;&lt;/span&gt; +%Y/%m&lt;span class="va"&gt;)&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="dt"&gt;\&lt;/span&gt;&lt;/span&gt; 1079 - &lt;span id="cb24-5"&gt;&lt;a href="#cb24-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="at"&gt;-i&lt;/span&gt; %Y/%m &lt;span class="dt"&gt;\&lt;/span&gt;&lt;/span&gt; 1080 - &lt;span id="cb24-6"&gt;&lt;a href="#cb24-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="at"&gt;-f&lt;/span&gt; %Y/%m&lt;/span&gt; 1081 - &lt;span id="cb24-7"&gt;&lt;a href="#cb24-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1082 - &lt;h3 id="fin"&gt;Fin&lt;/h3&gt; 1083 - &lt;p&gt;You can find a sample vimrc file here: &lt;a 1084 - href="https://git.peppe.rs/cli/journal/tree"&gt;cli/journal&lt;/a&gt;, along with 1085 - a nix flake file to kick things off.&lt;/p&gt; 1086 - &lt;p&gt;Plain text journaling can be just as much fun as a pen and paper. 1087 - Throw in some ASCII art for each month, use swankier signifiers, or 1088 - louder syntax highlighting. Don’t expect forgiveness from org-mode users 1089 - though.&lt;/p&gt; 1090 - &lt;p&gt;&lt;a href="https://u.peppe.rs/ZCK.png"&gt;&lt;img 1091 - src="https://u.peppe.rs/ZCK.png" /&gt;&lt;/a&gt;&lt;/p&gt;</description> 1092 - <link>https://oppi.li/posts/plain_text_journaling/</link> 1093 - <pubDate>Sun, 18 Jun 2023 19:40:00 +0000</pubDate> 1094 - <guid>https://oppi.li/posts/plain_text_journaling/</guid> 1095 - </item> 1096 - <item> 1097 - <title>Curing A Case Of Git-UX</title> 1098 - <description>&lt;p&gt;Git worktrees are great, but they fall behind the venerable 1099 - &lt;code&gt;git checkout&lt;/code&gt; sometimes. I attempted to fix that with &lt;a 1100 - href="https://github.com/junegunn/fzf"&gt;fzf&lt;/a&gt; and a bit of bash.&lt;/p&gt; 1101 - &lt;p&gt;&lt;a href="https://asciinema.org/a/D297ztKRzpE4gAHbPTPmkqYps"&gt;&lt;img 1102 - src="https://asciinema.org/a/D297ztKRzpE4gAHbPTPmkqYps.svg" /&gt;&lt;/a&gt;&lt;/p&gt; 1103 - &lt;p&gt;Fear not if you haven’t heard of “worktrees”, I have included a 1104 - primer here.&lt;br /&gt; 1105 - &lt;a href="#what-makes-them-clunky"&gt;Skip the primer -&amp;gt;&lt;/a&gt;.&lt;/p&gt; 1106 - &lt;h3 id="why-worktrees"&gt;Why Worktrees?&lt;/h3&gt; 1107 - &lt;p&gt;Picture this. You are whacking away on a feature branch. Halfway 1108 - there, in fact. Your friend asks you fix something urgently. You proceed 1109 - to do one of three things:&lt;/p&gt; 1110 - &lt;ul&gt; 1111 - &lt;li&gt;create a temporary branch, make a WIP commit, begin working on the 1112 - fix&lt;/li&gt; 1113 - &lt;li&gt;stash away your changes, begin working on the fix&lt;/li&gt; 1114 - &lt;li&gt;unfriend said friend for disturbing your flow&lt;/li&gt; 1115 - &lt;/ul&gt; 1116 - &lt;p&gt;All of these options are … subpar. With the temporary branch, you are 1117 - forced to create a partial, non-working commit, and then reset said 1118 - commit once done with the fix. With the stash approach, you are required 1119 - to now keep a mental model of the stash, be aware of untracked files 1120 - that don’t get stashed by default, etc. Why won’t git just let you work 1121 - on two things at the same time without &lt;em&gt;thinking&lt;/em&gt; so much?&lt;/p&gt; 1122 - &lt;p&gt;That is exactly what worktrees let you do. Worktrees let you have 1123 - more than one checkout at a time, each checkout in a separate directory. 1124 - Like creating a new clone, but safer (it disallows checking out the same 1125 - branch twice) and a lot more space efficient (the new working tree is 1126 - “linked” to the “main” worktree, and a good amount of stuff is shared). 1127 - When your friend asks you to make the fix, you proceed like so:&lt;/p&gt; 1128 - &lt;ol type="1"&gt; 1129 - &lt;li&gt;Create a new working tree with:&lt;/li&gt; 1130 - &lt;/ol&gt; 1131 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre 1132 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# git worktree add -b &amp;lt;branch-name&amp;gt; &amp;lt;path&amp;gt; &amp;lt;from&amp;gt;&lt;/span&gt;&lt;/span&gt; 1133 - &lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;git&lt;/span&gt; worktree add &lt;span class="at"&gt;-b&lt;/span&gt; fix-stuff /path/to/tree master&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1134 - &lt;ol start="2" type="1"&gt; 1135 - &lt;li&gt;&lt;code&gt;cd&lt;/code&gt; into &lt;code&gt;/path/to/tree&lt;/code&gt;&lt;/li&gt; 1136 - &lt;li&gt;Fix, test, commit, push, party&lt;/li&gt; 1137 - &lt;li&gt;Go back to your work, &lt;code&gt;cd -&lt;/code&gt;&lt;/li&gt; 1138 - &lt;/ol&gt; 1139 - &lt;p&gt;Easy as cake. You didn’t have to settle for a partially working 1140 - commit, you didn’t to deal with this “stash” thing, &lt;em&gt;and&lt;/em&gt; you 1141 - didn’t have to unfriend your friend. Treating each branch as a directory 1142 - just &lt;em&gt;feels&lt;/em&gt; more intuitive, more UNIX-y.&lt;/p&gt; 1143 - &lt;p&gt;A few weeks later, you find yourself singing in praise of worktrees, 1144 - working on several things simultaneously. And at the same time, cursing 1145 - them for being a little … clunky.&lt;/p&gt; 1146 - &lt;h3 id="what-makes-them-clunky"&gt;What makes them clunky?&lt;/h3&gt; 1147 - &lt;p&gt;Worktrees are great at what they claim to do. They stay out of the 1148 - way when you need a checkout posthaste. However, as you start using them 1149 - regularly, you realize they are not as flexible as 1150 - &lt;code&gt;git checkout&lt;/code&gt; or &lt;code&gt;git switch&lt;/code&gt;.&lt;/p&gt; 1151 - &lt;h4 id="branch-hopping"&gt;Branch-hopping&lt;/h4&gt; 1152 - &lt;p&gt;You can &lt;code&gt;git checkout &amp;lt;branch&amp;gt;&lt;/code&gt; from anywhere within 1153 - a git repository. You can’t “jump” to a worktree in the same fashion. 1154 - The closest you can get, is to run &lt;code&gt;git worktree list&lt;/code&gt;, copy 1155 - the path corresponding to your branch, and &lt;code&gt;cd&lt;/code&gt; into it.&lt;/p&gt; 1156 - &lt;p&gt;Branch-hopping with the good ol’ git-checkout:&lt;/p&gt; 1157 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 1158 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# anywhere, anytime&lt;/span&gt;&lt;/span&gt; 1159 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; git checkout feature/is-ascii-octdigit&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1160 - &lt;p&gt;Meanwhile, in worktree world:&lt;/p&gt; 1161 - &lt;div class="sourceCode" id="cb3"&gt;&lt;pre 1162 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb3-1"&gt;&lt;a href="#cb3-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# keeping these paths in your head is hard&lt;/span&gt;&lt;/span&gt; 1163 - &lt;span id="cb3-2"&gt;&lt;a href="#cb3-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; git worktree list&lt;/span&gt; 1164 - &lt;span id="cb3-3"&gt;&lt;a href="#cb3-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/worktrees/rustc/master&lt;/span&gt; eac6c33bc63 &lt;span class="pp"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;master&lt;/span&gt;&lt;span class="pp"&gt;]&lt;/span&gt;&lt;/span&gt; 1165 - &lt;span id="cb3-4"&gt;&lt;a href="#cb3-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/worktrees/rustc/improve-std-char-docs&lt;/span&gt; 94cba88553e &lt;span class="pp"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;improve&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;std&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;char&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;docs&lt;/span&gt;&lt;span class="pp"&gt;]&lt;/span&gt;&lt;/span&gt; 1166 - &lt;span id="cb3-5"&gt;&lt;a href="#cb3-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/worktrees/rustc/is-ascii-octdigit&lt;/span&gt; bc57be3af7a &lt;span class="pp"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;feature/is&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;ascii&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;octdigit&lt;/span&gt;&lt;span class="pp"&gt;]&lt;/span&gt;&lt;/span&gt; 1167 - &lt;span id="cb3-6"&gt;&lt;a href="#cb3-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/my/other/path/oh/god&lt;/span&gt; op57or3ns7n &lt;span class="pp"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;fix/some&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;error&lt;/span&gt;&lt;span class="pp"&gt;]&lt;/span&gt;&lt;/span&gt; 1168 - &lt;span id="cb3-7"&gt;&lt;a href="#cb3-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1169 - &lt;span id="cb3-8"&gt;&lt;a href="#cb3-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; cd ~/worktrees/rustc/is-ascii-octdigit&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1170 - &lt;h4 id="branch-previewing"&gt;Branch-previewing&lt;/h4&gt; 1171 - &lt;p&gt;You can “preview” branches with &lt;code&gt;git branch -v&lt;/code&gt;. However, 1172 - to get an idea of what “recent activity” on a worktree looks like, you 1173 - might need some juggling. You can’t glean much info about a worktree in 1174 - a jiffy.&lt;/p&gt; 1175 - &lt;p&gt;Branch-previewing with the good ol’ git-branch:&lt;/p&gt; 1176 - &lt;div class="sourceCode" id="cb4"&gt;&lt;pre 1177 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb4-1"&gt;&lt;a href="#cb4-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; git branch &lt;span class="at"&gt;-v&lt;/span&gt;&lt;/span&gt; 1178 - &lt;span id="cb4-2"&gt;&lt;a href="#cb4-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;+&lt;/span&gt; feature/is-ascii-octdigit bc57be3af7a introduce {char, u8}::is_ ...&lt;/span&gt; 1179 - &lt;span id="cb4-3"&gt;&lt;a href="#cb4-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;+&lt;/span&gt; improve-std-char-docs 94cba88553e add whitespace in assert ...&lt;/span&gt; 1180 - &lt;span id="cb4-4"&gt;&lt;a href="#cb4-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;*&lt;/span&gt; master eac6c33bc63 Auto merge of &lt;span class="co"&gt;#100869 - n ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1181 - &lt;p&gt;Meanwhile in worktree wonderland:&lt;/p&gt; 1182 - &lt;pre&gt;&lt;code&gt;λ git worktree list 1183 - ~/worktrees/rustc/master eac6c33bc63 [master] 1184 - ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 1185 - ~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit] 1186 - 1187 - # aha, so ../is-ascii-octdigit corresponds to `feature/is-ascii-octdigit` 1188 - λ git log feature/is-ascii-octdigit 1189 - bc57be3af7a introduce {char, u8}::is_ascii_octdigit 1190 - eac6c33bc63 Auto merge of #100869 - nnethercote:repl ... 1191 - b32223fec10 Auto merge of #100707 - dzvon:fix-typo, ... 1192 - aa857eb953e Auto merge of #100537 - petrochenkov:pic ... 1193 - 1194 - # extra work to make the branch &amp;lt;-&amp;gt; worktree correspondence&lt;/code&gt;&lt;/pre&gt; 1195 - &lt;h4 id="shell-completions"&gt;Shell completions&lt;/h4&gt; 1196 - &lt;p&gt;Lastly, you can bank on shell completions to fill in your branch 1197 - whilst using &lt;code&gt;git checkout&lt;/code&gt;. Worktrees have no such 1198 - conveniences.&lt;/p&gt; 1199 - &lt;p&gt;We can mend these minor faults with fzf.&lt;/p&gt; 1200 - &lt;h3 id="unclunkifying-worktrees"&gt;Unclunkifying worktrees&lt;/h3&gt; 1201 - &lt;p&gt;I’d suggest looking up &lt;a 1202 - href="https://github.com/junegunn/fzf"&gt;fzf&lt;/a&gt; (or &lt;a 1203 - href="https://github.com/lotabout/skim"&gt;skim&lt;/a&gt; or &lt;a 1204 - href="https://github.com/jhawthorn/fzy"&gt;fzy&lt;/a&gt;). These things make it 1205 - cake-easy to add interactivity to your shell. Onto fixing the first 1206 - minor fault, the inability to “jump” to a worktree from anywhere within 1207 - a git repository.&lt;/p&gt; 1208 - &lt;p&gt;I have a little function called &lt;code&gt;gwj&lt;/code&gt; which stands for 1209 - “git worktree jump”. The idea is to list all the worktrees, select one 1210 - with fzf, and &lt;code&gt;cd&lt;/code&gt; to it upon selection:&lt;/p&gt; 1211 - &lt;div class="sourceCode" id="cb6"&gt;&lt;pre 1212 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb6-1"&gt;&lt;a href="#cb6-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;gwj ()&lt;/span&gt; &lt;span class="kw"&gt;{&lt;/span&gt;&lt;/span&gt; 1213 - &lt;span id="cb6-2"&gt;&lt;a href="#cb6-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="bu"&gt;local&lt;/span&gt; &lt;span class="va"&gt;out&lt;/span&gt;&lt;/span&gt; 1214 - &lt;span id="cb6-3"&gt;&lt;a href="#cb6-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;out&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="fu"&gt;git&lt;/span&gt; worktree list &lt;span class="kw"&gt;|&lt;/span&gt; &lt;span class="ex"&gt;fzf&lt;/span&gt; &lt;span class="kw"&gt;|&lt;/span&gt; &lt;span class="fu"&gt;awk&lt;/span&gt; &lt;span class="st"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt;&lt;span class="va"&gt;)&lt;/span&gt;&lt;/span&gt; 1215 - &lt;span id="cb6-4"&gt;&lt;a href="#cb6-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="bu"&gt;cd&lt;/span&gt; &lt;span class="va"&gt;$out&lt;/span&gt;&lt;/span&gt; 1216 - &lt;span id="cb6-5"&gt;&lt;a href="#cb6-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1217 - &lt;p&gt;That is all of it really. Head into a git repository:&lt;/p&gt; 1218 - &lt;div class="sourceCode" id="cb7"&gt;&lt;pre 1219 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb7-1"&gt;&lt;a href="#cb7-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# here, &amp;quot;master&amp;quot; is a directory, which contains my main&lt;/span&gt;&lt;/span&gt; 1220 - &lt;span id="cb7-2"&gt;&lt;a href="#cb7-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# worktree: a checkout of the master branch on rust-lang/rust &lt;/span&gt;&lt;/span&gt; 1221 - &lt;span id="cb7-3"&gt;&lt;a href="#cb7-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; cd ~/worktrees/rustc/master/library/core/src&lt;/span&gt; 1222 - &lt;span id="cb7-4"&gt;&lt;a href="#cb7-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; &lt;span class="co"&gt;# hack away&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1223 - &lt;p&gt;Preferably one with a few worktrees:&lt;/p&gt; 1224 - &lt;div class="sourceCode" id="cb8"&gt;&lt;pre 1225 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb8-1"&gt;&lt;a href="#cb8-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; git worktree list&lt;/span&gt; 1226 - &lt;span id="cb8-2"&gt;&lt;a href="#cb8-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/worktrees/rustc/master&lt;/span&gt; eac6c33bc63 &lt;span class="pp"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;master&lt;/span&gt;&lt;span class="pp"&gt;]&lt;/span&gt;&lt;/span&gt; 1227 - &lt;span id="cb8-3"&gt;&lt;a href="#cb8-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/worktrees/rustc/improve-std-char-docs&lt;/span&gt; 94cba88553e &lt;span class="pp"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;improve&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;std&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;char&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;docs&lt;/span&gt;&lt;span class="pp"&gt;]&lt;/span&gt;&lt;/span&gt; 1228 - &lt;span id="cb8-4"&gt;&lt;a href="#cb8-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;~/worktrees/rustc/is-ascii-octdigit&lt;/span&gt; bc57be3af7a &lt;span class="pp"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;feature/is&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;ascii&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;octdigit&lt;/span&gt;&lt;span class="pp"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1229 - &lt;p&gt;And hit &lt;code&gt;gwj&lt;/code&gt; (pretend that the pipe, |, is your 1230 - cursor):&lt;/p&gt; 1231 - &lt;div class="sourceCode" id="cb9"&gt;&lt;pre 1232 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb9-1"&gt;&lt;a href="#cb9-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; gwj&lt;/span&gt; 1233 - &lt;span id="cb9-2"&gt;&lt;a href="#cb9-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; &lt;span class="kw"&gt;|&lt;/span&gt;&lt;/span&gt; 1234 - &lt;span id="cb9-3"&gt;&lt;a href="#cb9-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;4/4&lt;/span&gt;&lt;/span&gt; 1235 - &lt;span id="cb9-4"&gt;&lt;a href="#cb9-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; ~/worktrees/rustc/master &lt;span class="ex"&gt;eac6c33bc63&lt;/span&gt; &lt;span class="pp"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;master&lt;/span&gt;&lt;span class="pp"&gt;]&lt;/span&gt;&lt;/span&gt; 1236 - &lt;span id="cb9-5"&gt;&lt;a href="#cb9-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;~/worktrees/rustc/improve-std-char-docs&lt;/span&gt; 94cba88553e &lt;span class="pp"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;improve&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;std&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;char&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;docs&lt;/span&gt;&lt;span class="pp"&gt;]&lt;/span&gt;&lt;/span&gt; 1237 - &lt;span id="cb9-6"&gt;&lt;a href="#cb9-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;~/worktrees/rustc/is-ascii-octdigit&lt;/span&gt; bc57be3af7a &lt;span class="pp"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;feature/is&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;ascii&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;octdigit&lt;/span&gt;&lt;span class="pp"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1238 - &lt;p&gt;Approximately type in your branch of choice:&lt;/p&gt; 1239 - &lt;div class="sourceCode" id="cb10"&gt;&lt;pre 1240 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb10-1"&gt;&lt;a href="#cb10-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; gwj&lt;/span&gt; 1241 - &lt;span id="cb10-2"&gt;&lt;a href="#cb10-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; docs&lt;span class="kw"&gt;|&lt;/span&gt;&lt;/span&gt; 1242 - &lt;span id="cb10-3"&gt;&lt;a href="#cb10-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;4/4&lt;/span&gt;&lt;/span&gt; 1243 - &lt;span id="cb10-4"&gt;&lt;a href="#cb10-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; ~/worktrees/rustc/improve-std-char-docs &lt;span class="ex"&gt;94cba88553e&lt;/span&gt; &lt;span class="pp"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;improve&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;std&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;char&lt;/span&gt;&lt;span class="pp"&gt;-&lt;/span&gt;&lt;span class="ss"&gt;docs&lt;/span&gt;&lt;span class="pp"&gt;]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1244 - &lt;p&gt;And hit enter. You should find yourself in the selected worktree.&lt;/p&gt; 1245 - &lt;p&gt;Onward, to the next fault, lack of preview-bility. We can utilize 1246 - fzf’s aptly named &lt;code&gt;--preview&lt;/code&gt; flag, to, well, preview our 1247 - worktree before performing a selection:&lt;/p&gt; 1248 - &lt;div class="sourceCode" id="cb11"&gt;&lt;pre 1249 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb11-1"&gt;&lt;a href="#cb11-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;gwj ()&lt;/span&gt; &lt;span class="kw"&gt;{&lt;/span&gt;&lt;/span&gt; 1250 - &lt;span id="cb11-2"&gt;&lt;a href="#cb11-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="bu"&gt;local&lt;/span&gt; &lt;span class="va"&gt;out&lt;/span&gt;&lt;/span&gt; 1251 - &lt;span id="cb11-3"&gt;&lt;a href="#cb11-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;out&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;/span&gt; 1252 - &lt;span id="cb11-4"&gt;&lt;a href="#cb11-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="fu"&gt;git&lt;/span&gt; worktree list &lt;span class="kw"&gt;|&lt;/span&gt;&lt;/span&gt; 1253 - &lt;span id="cb11-5"&gt;&lt;a href="#cb11-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;fzf&lt;/span&gt; &lt;span class="at"&gt;--preview&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;git log --oneline -n10 {2}&amp;#39;&lt;/span&gt; &lt;span class="kw"&gt;|&lt;/span&gt;&lt;/span&gt; 1254 - &lt;span id="cb11-6"&gt;&lt;a href="#cb11-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="fu"&gt;awk&lt;/span&gt; &lt;span class="st"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt;&lt;/span&gt; 1255 - &lt;span id="cb11-7"&gt;&lt;a href="#cb11-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;)&lt;/span&gt;&lt;/span&gt; 1256 - &lt;span id="cb11-8"&gt;&lt;a href="#cb11-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="bu"&gt;cd&lt;/span&gt; &lt;span class="va"&gt;$out&lt;/span&gt;&lt;/span&gt; 1257 - &lt;span id="cb11-9"&gt;&lt;a href="#cb11-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1258 - &lt;p&gt;Once again, hit &lt;code&gt;gwj&lt;/code&gt; inside a git repository with linked 1259 - worktrees:&lt;/p&gt; 1260 - &lt;div class="sourceCode" id="cb12"&gt;&lt;pre 1261 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb12-1"&gt;&lt;a href="#cb12-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; gwj&lt;/span&gt; 1262 - &lt;span id="cb12-2"&gt;&lt;a href="#cb12-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;╭─────────────────────────────────────────────────────────╮&lt;/span&gt;&lt;/span&gt; 1263 - &lt;span id="cb12-3"&gt;&lt;a href="#cb12-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; eac6c33bc63 Auto merge of 100869 nnethercote:replace... │&lt;/span&gt; 1264 - &lt;span id="cb12-4"&gt;&lt;a href="#cb12-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; b32223fec10 Auto merge of 100707 dzvon:fix-typo, r=d... │&lt;/span&gt; 1265 - &lt;span id="cb12-5"&gt;&lt;a href="#cb12-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; aa857eb953e Auto merge of 100537 petrochenkov:picche... │&lt;/span&gt; 1266 - &lt;span id="cb12-6"&gt;&lt;a href="#cb12-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; 3892b7074da Auto merge of 100210 mystor:proc_macro_d... │&lt;/span&gt; 1267 - &lt;span id="cb12-7"&gt;&lt;a href="#cb12-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; db00199d999 Auto merge of 101249 matthiaskrgr:rollup... │&lt;/span&gt; 1268 - &lt;span id="cb12-8"&gt;&lt;a href="#cb12-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; 14d216d33ba Rollup merge of 101240 JohnTitor:JohnTit... │&lt;/span&gt; 1269 - &lt;span id="cb12-9"&gt;&lt;a href="#cb12-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; 3da66f03531 Rollup merge of 101236 thomcc:winfs-noze... │&lt;/span&gt; 1270 - &lt;span id="cb12-10"&gt;&lt;a href="#cb12-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; 0620f6e90af Rollup merge of 101230 davidtwco:transla... │&lt;/span&gt; 1271 - &lt;span id="cb12-11"&gt;&lt;a href="#cb12-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; c30c42ee299 Rollup merge of 101229 mgeisler:link-try... │&lt;/span&gt; 1272 - &lt;span id="cb12-12"&gt;&lt;a href="#cb12-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; e5356712b9e Rollup merge of 101165 ldm0:drain_to_ite... │&lt;/span&gt; 1273 - &lt;span id="cb12-13"&gt;&lt;a href="#cb12-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;╰─────────────────────────────────────────────────────────╯&lt;/span&gt;&lt;/span&gt; 1274 - &lt;span id="cb12-14"&gt;&lt;a href="#cb12-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt; 1275 - &lt;span id="cb12-15"&gt;&lt;a href="#cb12-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;4/4&lt;/span&gt;&lt;/span&gt; 1276 - &lt;span id="cb12-16"&gt;&lt;a href="#cb12-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; /home/np/worktrees/compiler/master &lt;span class="ex"&gt;eac6c...&lt;/span&gt;&lt;/span&gt; 1277 - &lt;span id="cb12-17"&gt;&lt;a href="#cb12-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;/home/np/worktrees/compiler/improve-std-char-docs&lt;/span&gt; 94cba...&lt;/span&gt; 1278 - &lt;span id="cb12-18"&gt;&lt;a href="#cb12-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;/home/np/worktrees/compiler/is-ascii-octdigit&lt;/span&gt; bc57b...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1279 - &lt;p&gt;A fancy preview of the last 10 commits on the branch that the 1280 - selected worktree corresponds to. In other words, sight for sore eyes. 1281 - Our little script is already shaping up to be useful, you hit 1282 - &lt;code&gt;gwj&lt;/code&gt;, browse through your worktrees, preview each one and 1283 - automatically &lt;code&gt;cd&lt;/code&gt; to your selection. But we are not done 1284 - yet.&lt;/p&gt; 1285 - &lt;p&gt;The last fault was lack shell completions. A quick review of what a 1286 - shell completion really does:&lt;/p&gt; 1287 - &lt;div class="sourceCode" id="cb13"&gt;&lt;pre 1288 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb13-1"&gt;&lt;a href="#cb13-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; git checkout f&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;tab&lt;span class="op"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt; 1289 - &lt;span id="cb13-2"&gt;&lt;a href="#cb13-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;feature/is-ascii-octdigit&lt;/span&gt;&lt;/span&gt; 1290 - &lt;span id="cb13-3"&gt;&lt;a href="#cb13-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;fix/some-error&lt;/span&gt;&lt;/span&gt; 1291 - &lt;span id="cb13-4"&gt;&lt;a href="#cb13-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;format-doc-tests&lt;/span&gt;&lt;/span&gt; 1292 - &lt;span id="cb13-5"&gt;&lt;a href="#cb13-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1293 - &lt;span id="cb13-6"&gt;&lt;a href="#cb13-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; git checkout feat&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;tab&lt;span class="op"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt; 1294 - &lt;span id="cb13-7"&gt;&lt;a href="#cb13-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1295 - &lt;span id="cb13-8"&gt;&lt;a href="#cb13-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; git checkout feature/is-ascii-octdigit&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1296 - &lt;p&gt;Each time you hit “tab”, the shell produces a few “completion 1297 - candidates”, and once you have just a single candidate left, the shell 1298 - inserts that for you directly into your edit line. Of course, this 1299 - process varies from shell to shell.&lt;/p&gt; 1300 - &lt;p&gt;fzf narrows down your options as you type into the prompt, but you 1301 - still have to:&lt;/p&gt; 1302 - &lt;ol type="1"&gt; 1303 - &lt;li&gt;Type &lt;code&gt;gwj&lt;/code&gt;&lt;/li&gt; 1304 - &lt;li&gt;Hit enter&lt;/li&gt; 1305 - &lt;li&gt;Type out a query and narrow down your search&lt;/li&gt; 1306 - &lt;li&gt;Hit enter&lt;/li&gt; 1307 - &lt;/ol&gt; 1308 - &lt;p&gt;We can speed that up a bit, have fzf narrow down the candidates on 1309 - startup, just like our shell does:&lt;/p&gt; 1310 - &lt;div class="sourceCode" id="cb14"&gt;&lt;pre 1311 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb14-1"&gt;&lt;a href="#cb14-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;gwj ()&lt;/span&gt; &lt;span class="kw"&gt;{&lt;/span&gt;&lt;/span&gt; 1312 - &lt;span id="cb14-2"&gt;&lt;a href="#cb14-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="bu"&gt;local&lt;/span&gt; &lt;span class="va"&gt;out&lt;/span&gt; &lt;span class="va"&gt;query&lt;/span&gt;&lt;/span&gt; 1313 - &lt;span id="cb14-3"&gt;&lt;a href="#cb14-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;query&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;${1&lt;/span&gt;&lt;span class="op"&gt;:-&lt;/span&gt; &lt;span class="va"&gt;}&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; 1314 - &lt;span id="cb14-4"&gt;&lt;a href="#cb14-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;out&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;/span&gt; 1315 - &lt;span id="cb14-5"&gt;&lt;a href="#cb14-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="fu"&gt;git&lt;/span&gt; worktree list &lt;span class="kw"&gt;|&lt;/span&gt;&lt;/span&gt; 1316 - &lt;span id="cb14-6"&gt;&lt;a href="#cb14-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;fzf&lt;/span&gt; &lt;span class="at"&gt;--preview&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;#39;git log --oneline -n10 {2}&amp;#39;&lt;/span&gt; &lt;span class="at"&gt;--query&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$query&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="at"&gt;-1&lt;/span&gt; &lt;span class="kw"&gt;|&lt;/span&gt;&lt;/span&gt; 1317 - &lt;span id="cb14-7"&gt;&lt;a href="#cb14-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="fu"&gt;awk&lt;/span&gt; &lt;span class="st"&gt;&amp;#39;{print $1}&amp;#39;&lt;/span&gt;&lt;/span&gt; 1318 - &lt;span id="cb14-8"&gt;&lt;a href="#cb14-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;)&lt;/span&gt;&lt;/span&gt; 1319 - &lt;span id="cb14-9"&gt;&lt;a href="#cb14-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="bu"&gt;cd&lt;/span&gt; &lt;span class="va"&gt;$out&lt;/span&gt;&lt;/span&gt; 1320 - &lt;span id="cb14-10"&gt;&lt;a href="#cb14-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1321 - &lt;p&gt;The change is extremely tiny, blink-and-you’ll-miss-it kinda tiny. We 1322 - added a little &lt;code&gt;--query&lt;/code&gt; flag, that allows you to prefill the 1323 - prompt, and the &lt;code&gt;-1&lt;/code&gt; flag, that avoids the interactive finder 1324 - if only one match exists on startup:&lt;/p&gt; 1325 - &lt;div class="sourceCode" id="cb15"&gt;&lt;pre 1326 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb15-1"&gt;&lt;a href="#cb15-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# skip through the fzf prompt:&lt;/span&gt;&lt;/span&gt; 1327 - &lt;span id="cb15-2"&gt;&lt;a href="#cb15-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; gwj master&lt;/span&gt; 1328 - &lt;span id="cb15-3"&gt;&lt;a href="#cb15-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# cd -- ~/worktrees/rustc/master&lt;/span&gt;&lt;/span&gt; 1329 - &lt;span id="cb15-4"&gt;&lt;a href="#cb15-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1330 - &lt;span id="cb15-5"&gt;&lt;a href="#cb15-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# more than one option, we end up in the interactive finder&lt;/span&gt;&lt;/span&gt; 1331 - &lt;span id="cb15-6"&gt;&lt;a href="#cb15-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;λ&lt;/span&gt; gwj improve&lt;/span&gt; 1332 - &lt;span id="cb15-7"&gt;&lt;a href="#cb15-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;╭─────────────────────────────────────────────────────────╮&lt;/span&gt;&lt;/span&gt; 1333 - &lt;span id="cb15-8"&gt;&lt;a href="#cb15-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; eac6c33bc63 Auto merge of 100869 nnethercote:replace... │&lt;/span&gt; 1334 - &lt;span id="cb15-9"&gt;&lt;a href="#cb15-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; b32223fec10 Auto merge of 100707 dzvon:fix-typo, r=d... │&lt;/span&gt; 1335 - &lt;span id="cb15-10"&gt;&lt;a href="#cb15-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│&lt;/span&gt; aa857eb953e Auto merge of 100537 petrochenkov:picche... │&lt;/span&gt; 1336 - &lt;span id="cb15-11"&gt;&lt;a href="#cb15-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;╰─────────────────────────────────────────────────────────╯&lt;/span&gt;&lt;/span&gt; 1337 - &lt;span id="cb15-12"&gt;&lt;a href="#cb15-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; improve&lt;/span&gt; 1338 - &lt;span id="cb15-13"&gt;&lt;a href="#cb15-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;2/2&lt;/span&gt;&lt;/span&gt; 1339 - &lt;span id="cb15-14"&gt;&lt;a href="#cb15-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; /home/np/worktrees/compiler/improve-const-perf &lt;span class="ex"&gt;eac6c...&lt;/span&gt;&lt;/span&gt; 1340 - &lt;span id="cb15-15"&gt;&lt;a href="#cb15-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;/home/np/worktrees/compiler/improve-std-char-docs&lt;/span&gt; 94cba...&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1341 - &lt;p&gt;Throw some error handling in there, hook up a similar script to 1342 - improve the UX of &lt;code&gt;git worktree remove&lt;/code&gt;, go wild. A few more 1343 - helpers I’ve got:&lt;/p&gt; 1344 - &lt;div class="sourceCode" id="cb16"&gt;&lt;pre 1345 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb16-1"&gt;&lt;a href="#cb16-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# gwa /path/to/branch-name&lt;/span&gt;&lt;/span&gt; 1346 - &lt;span id="cb16-2"&gt;&lt;a href="#cb16-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# creates a new branch and &amp;quot;switches&amp;quot; to it&lt;/span&gt;&lt;/span&gt; 1347 - &lt;span id="cb16-3"&gt;&lt;a href="#cb16-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;function&lt;/span&gt;&lt;span class="fu"&gt; gwa ()&lt;/span&gt; &lt;span class="kw"&gt;{&lt;/span&gt;&lt;/span&gt; 1348 - &lt;span id="cb16-4"&gt;&lt;a href="#cb16-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="fu"&gt;git&lt;/span&gt; worktree add &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$1&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;span class="kw"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="bu"&gt;cd&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$1&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; 1349 - &lt;span id="cb16-5"&gt;&lt;a href="#cb16-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;}&lt;/span&gt;&lt;/span&gt; 1350 - &lt;span id="cb16-6"&gt;&lt;a href="#cb16-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1351 - &lt;span id="cb16-7"&gt;&lt;a href="#cb16-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;alias&lt;/span&gt; gwls=&lt;span class="st"&gt;&amp;quot;git worktree list&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description> 1352 - <link>https://oppi.li/posts/curing_a_case_of_git-UX/</link> 1353 - <pubDate>Sat, 03 Sep 2022 03:18:00 +0000</pubDate> 1354 - <guid>https://oppi.li/posts/curing_a_case_of_git-UX/</guid> 1355 - </item> 1356 - <item> 1357 - <title>Programming On 34 Keys</title> 1358 - <description>&lt;p&gt;Minimizing your keyboard layout is a slippery slope. A few months 1359 - ago, I built the &lt;a 1360 - href="https://github.com/icyphox/ferricy"&gt;Ferricy&lt;/a&gt;, a 1361 - 34-key-split-ortho-ergo keyboard. The Ferricy is a fork of the &lt;a 1362 - href="https://github.com/davidphilipbarr/Sweep/tree/main/Sweep%20Bling%20MX"&gt;Ferris 1363 - Sweep MX Bling&lt;/a&gt;.&lt;/p&gt; 1364 - &lt;figure&gt; 1365 - &lt;img src="https://u.peppe.rs/otz.jpg" 1366 - alt="The Ferricy, designed by icyphox" /&gt; 1367 - &lt;figcaption aria-hidden="true"&gt;The Ferricy, designed by &lt;a 1368 - href="https://icyphox.sh"&gt;icyphox&lt;/a&gt;&lt;/figcaption&gt; 1369 - &lt;/figure&gt; 1370 - &lt;p&gt;My daily use consists of a bit of prose and a lot of program, my 1371 - layout has evolved accordingly.&lt;/p&gt; 1372 - &lt;h1 id="base-layer"&gt;Base Layer&lt;/h1&gt; 1373 - &lt;figure&gt; 1374 - &lt;img src="https://u.peppe.rs/base.png" alt="Colemak with no mods" /&gt; 1375 - &lt;figcaption aria-hidden="true"&gt;Colemak with no mods&lt;/figcaption&gt; 1376 - &lt;/figure&gt; 1377 - &lt;p&gt;The base layer contains alphabets, four symbols and four whitespace 1378 - keys:&lt;/p&gt; 1379 - &lt;ul&gt; 1380 - &lt;li&gt;Alphas: Stock Colemak, with no modifications whatsoever&lt;/li&gt; 1381 - &lt;li&gt;Symbols: &lt;code&gt;. , / ;&lt;/code&gt;&lt;/li&gt; 1382 - &lt;li&gt;Whitespace: tab, space, enter, backspace (from left to right)&lt;/li&gt; 1383 - &lt;/ul&gt; 1384 - &lt;h1 id="layers"&gt;Layers&lt;/h1&gt; 1385 - &lt;p&gt;Keyboard input is complex and it is impossible to skirt around it. 1386 - You can either use a keyboard with enough keys to supply all possible 1387 - inputs (a mechanical burden), or you can use firmware to supply all 1388 - possible inputs (a cognitive burden). Layers are a cognitive burden.&lt;/p&gt; 1389 - &lt;p&gt;I use 3 layers, heavily inspired by &lt;a 1390 - href="https://github.com/manna-harbour/miryoku"&gt;Miryoku&lt;/a&gt;, but tuned 1391 - for programming. Excluding the base Colemak layer:&lt;/p&gt; 1392 - &lt;ul&gt; 1393 - &lt;li&gt;&lt;code&gt;NAV&lt;/code&gt;: activated on holding &lt;code&gt;space&lt;/code&gt; (left 1394 - thumb)&lt;/li&gt; 1395 - &lt;li&gt;&lt;code&gt;NUM&lt;/code&gt;: activated on holding &lt;code&gt;tab&lt;/code&gt; (left 1396 - thumb)&lt;/li&gt; 1397 - &lt;li&gt;&lt;code&gt;SYM&lt;/code&gt;: activated on holding &lt;code&gt;enter&lt;/code&gt; (right 1398 - thumb)&lt;/li&gt; 1399 - &lt;/ul&gt; 1400 - &lt;h2 id="the-nav-layer"&gt;The &lt;code&gt;NAV&lt;/code&gt; Layer&lt;/h2&gt; 1401 - &lt;p&gt;As the name suggests, this layer is focused on navigation. Arrow keys 1402 - and the likes.&lt;/p&gt; 1403 - &lt;figure&gt; 1404 - &lt;img src="https://u.peppe.rs/nav.png" alt="NAV, on holding space" /&gt; 1405 - &lt;figcaption aria-hidden="true"&gt;&lt;code&gt;NAV&lt;/code&gt;, on holding 1406 - &lt;code&gt;space&lt;/code&gt;&lt;/figcaption&gt; 1407 - &lt;/figure&gt; 1408 - &lt;p&gt;Using Vim and Colemak means you lose out on HJKL navigation. However, 1409 - on activating the &lt;code&gt;NAV&lt;/code&gt; layer, the right home-row is 1410 - converted into arrow keys. In essence, by holding space, I can navigate 1411 - Vim with the home-row, or Firefox, or my PDF reader. I no longer need to 1412 - look for software that allows Vim navigation keys, because it is baked 1413 - into the firmware!&lt;/p&gt; 1414 - &lt;p&gt;My Vim motions are not limited to HJKL. In fact, my Vim motions are 1415 - rarely HJKL. I tend to use &lt;code&gt;}&lt;/code&gt; (next paragraph) and 1416 - &lt;code&gt;)&lt;/code&gt; (next sentence) more often. As a result, these have found 1417 - their way into my &lt;code&gt;NAV&lt;/code&gt; layer, over the likes of 1418 - &lt;code&gt;PgDown&lt;/code&gt; and &lt;code&gt;End&lt;/code&gt;. Having brackets at my index 1419 - and middle fingers is nice for programming too.&lt;/p&gt; 1420 - &lt;h2 id="the-sym-layer"&gt;The &lt;code&gt;SYM&lt;/code&gt; Layer&lt;/h2&gt; 1421 - &lt;figure&gt; 1422 - &lt;img src="https://u.peppe.rs/sym.png" alt="SYM, on holding enter" /&gt; 1423 - &lt;figcaption aria-hidden="true"&gt;&lt;code&gt;SYM&lt;/code&gt;, on holding 1424 - &lt;code&gt;enter&lt;/code&gt;&lt;/figcaption&gt; 1425 - &lt;/figure&gt; 1426 - &lt;p&gt;This layer contains all the symbols that you would find by hitting 1427 - &lt;code&gt;Shift&lt;/code&gt; and a key on the number row. Probably noteworthy to 1428 - Vim users: the symbols are arranged in the form of a mirrored numpad for 1429 - exactly one reason: to move &lt;code&gt;$&lt;/code&gt; to the left of 1430 - &lt;code&gt;^&lt;/code&gt;. It has always annoyed me that &lt;code&gt;$&lt;/code&gt; moves the 1431 - cursor to the end of the line and &lt;code&gt;^&lt;/code&gt; moves it to the 1432 - beginning, but their position on a typical number row are reversed, 4 1433 - comes before 6.&lt;/p&gt; 1434 - &lt;h2 id="the-num-layer"&gt;The &lt;code&gt;NUM&lt;/code&gt; layer&lt;/h2&gt; 1435 - &lt;figure&gt; 1436 - &lt;img src="https://u.peppe.rs/num.png" alt="NUM, on holding tab" /&gt; 1437 - &lt;figcaption aria-hidden="true"&gt;&lt;code&gt;NUM&lt;/code&gt;, on holding 1438 - &lt;code&gt;tab&lt;/code&gt;&lt;/figcaption&gt; 1439 - &lt;/figure&gt; 1440 - &lt;p&gt;Another deviation from Miryoku, the numpad just feels &lt;em&gt;right&lt;/em&gt; 1441 - on my &lt;em&gt;right&lt;/em&gt; hand.&lt;/p&gt; 1442 - &lt;h1 id="zmk-combos"&gt;ZMK Combos&lt;/h1&gt; 1443 - &lt;p&gt;If you have been paying close attention, you might have noticed that 1444 - &lt;code&gt;escape&lt;/code&gt; didn’t make it to any layer. &lt;code&gt;escape&lt;/code&gt; is 1445 - too crucial to put on a non-base layer, but at the same time, not as 1446 - important to deserve a place on the base layer. That is where ZMK’s 1447 - combos come in. Combos let you tap any number of keys, and combine them 1448 - to form a single key. I have combos set up for underscore, minus, escape 1449 - and caps-word (more on caps-word later):&lt;/p&gt; 1450 - &lt;figure&gt; 1451 - &lt;img src="https://u.peppe.rs/combos.png" 1452 - alt="Combos are almost piano-like" /&gt; 1453 - &lt;figcaption aria-hidden="true"&gt;Combos are almost piano-like&lt;/figcaption&gt; 1454 - &lt;/figure&gt; 1455 - &lt;h1 id="home-row-mods"&gt;Home-row Mods&lt;/h1&gt; 1456 - &lt;p&gt;Inherited from Miryoku, I have home-row mods for activating 1457 - &lt;code&gt;Super&lt;/code&gt;, &lt;code&gt;Alt&lt;/code&gt;, &lt;code&gt;Shift&lt;/code&gt;, 1458 - &lt;code&gt;Ctrl&lt;/code&gt; and &lt;code&gt;Hyper&lt;/code&gt; 1459 - (&lt;code&gt;Ctrl + Shift + Alt + Super&lt;/code&gt;). The idea is to send 1460 - &lt;code&gt;T&lt;/code&gt; on tap and &lt;code&gt;Ctrl&lt;/code&gt; on hold. Home-row mods are 1461 - fairly popular, so I’ll not go into the details.&lt;/p&gt; 1462 - &lt;figure&gt; 1463 - &lt;img src="https://u.peppe.rs/homerow.png" 1464 - alt="Super, Alt, Shift, Ctrl, Hyper; on the left half, and mirrored on the right half" /&gt; 1465 - &lt;figcaption aria-hidden="true"&gt;Super, Alt, Shift, Ctrl, Hyper; on the 1466 - left half, and mirrored on the right half&lt;/figcaption&gt; 1467 - &lt;/figure&gt; 1468 - &lt;p&gt;&lt;code&gt;Hyper&lt;/code&gt; bridges the gap between firmware and software. You 1469 - can never configure key combination that, opens Firefox, for example, 1470 - through firmware alone. However, with the &lt;code&gt;Hyper&lt;/code&gt; key, and 1471 - some &lt;code&gt;sxhkd&lt;/code&gt; magic, you can emulate that. Pressing 1472 - &lt;code&gt;Hyper + F&lt;/code&gt; on a keyboard is just two keys, but the key codes 1473 - sent are &lt;code&gt;Ctrl + Shift + Alt + Super + F&lt;/code&gt;. That key 1474 - combination is not intercepted by any application as a shortcut, except 1475 - for the following &lt;code&gt;sxhkd&lt;/code&gt; stanza:&lt;/p&gt; 1476 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre 1477 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;super&lt;/span&gt; + alt + shift + ctrl + f&lt;/span&gt; 1478 - &lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;xdotool&lt;/span&gt; search &lt;span class="st"&gt;&amp;quot;Mozilla Firefox&amp;quot;&lt;/span&gt; windowactivate&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1479 - &lt;p&gt;Alternatively, you can intercept unused &lt;code&gt;F&lt;/code&gt; keys: 1480 - &lt;code&gt;F13&lt;/code&gt; through &lt;code&gt;F24&lt;/code&gt;.&lt;/p&gt; 1481 - &lt;p&gt;Home-row mods are mirrored on each half because it would be 1482 - impossible to hit &lt;code&gt;Ctrl + T&lt;/code&gt; if not; they lie on the same 1483 - key.&lt;/p&gt; 1484 - &lt;h1 id="caps-word"&gt;Caps-word&lt;/h1&gt; 1485 - &lt;p&gt;Caps-word is a clever caps-lock, built into ZMK. Typing out constants 1486 - such as &lt;code&gt;PORT&lt;/code&gt; with home-row mods would look like this:&lt;/p&gt; 1487 - &lt;ul&gt; 1488 - &lt;li&gt;hold &lt;code&gt;e&lt;/code&gt; (shift) on left hand, and tap &lt;code&gt;p&lt;/code&gt; on 1489 - right hand&lt;/li&gt; 1490 - &lt;li&gt;hold &lt;code&gt;e&lt;/code&gt; (shift) on left hand, and tap &lt;code&gt;o&lt;/code&gt; on 1491 - right hand&lt;/li&gt; 1492 - &lt;li&gt;hold &lt;code&gt;s&lt;/code&gt; (shift) on right hand, and tap &lt;code&gt;r&lt;/code&gt; on 1493 - left hand&lt;/li&gt; 1494 - &lt;li&gt;hold &lt;code&gt;s&lt;/code&gt; (shift) on right hand, and tap &lt;code&gt;t&lt;/code&gt; on 1495 - left hand&lt;/li&gt; 1496 - &lt;/ul&gt; 1497 - &lt;p&gt;This hold-alternate-hold dance gets tiring quickly. With caps-word, 1498 - however:&lt;/p&gt; 1499 - &lt;ul&gt; 1500 - &lt;li&gt;toggle &lt;code&gt;caps_word&lt;/code&gt;&lt;/li&gt; 1501 - &lt;li&gt;type out &lt;code&gt;p&lt;/code&gt;, &lt;code&gt;o&lt;/code&gt;, &lt;code&gt;r&lt;/code&gt;, 1502 - &lt;code&gt;t&lt;/code&gt;&lt;/li&gt; 1503 - &lt;li&gt;hit a &lt;em&gt;break&lt;/em&gt; character (space, enter will do)&lt;/li&gt; 1504 - &lt;li&gt;continue&lt;/li&gt; 1505 - &lt;/ul&gt; 1506 - &lt;p&gt;Caps-word automatically disables capitalization upon encountering a 1507 - breaking character, (which are space, enter or any modifier, by default) 1508 - right in the firmware!&lt;/p&gt; 1509 - &lt;h1 id="findings"&gt;Findings&lt;/h1&gt; 1510 - &lt;p&gt;34-keys has been reasonably comfortable to use, for both prose and 1511 - program. My palms do not move across the desk at all, as I reach for 1512 - keys. I mostly write Rust and Bash, and my layout has evolved to 1513 - accomodate special characters from their grammars (angled brackets and 1514 - hyphens, specifically). If you are on a similar journey, I would suggest 1515 - focusing on accuracy and comfort over speed. Speed comes with time.&lt;/p&gt;</description> 1516 - <link>https://oppi.li/posts/programming_on_34_keys/</link> 1517 - <pubDate>Sun, 28 Aug 2022 13:51:00 +0000</pubDate> 1518 - <guid>https://oppi.li/posts/programming_on_34_keys/</guid> 1519 - </item> 1520 - <item> 1521 - <title>A Reference Counted Afterlife</title> 1522 - <description>&lt;p&gt;I took interest in the Egyptian rendition of the afterlife 1523 - recently.&lt;/p&gt; 1524 - &lt;h3 id="parts-of-the-soul"&gt;Parts of the Soul&lt;/h3&gt; 1525 - &lt;p&gt;Ancient Egyptians believed that the soul comprised of several 1526 - components:&lt;/p&gt; 1527 - &lt;ul&gt; 1528 - &lt;li&gt;&lt;em&gt;ren&lt;/em&gt;&lt;/li&gt; 1529 - &lt;li&gt;&lt;em&gt;ka&lt;/em&gt;&lt;/li&gt; 1530 - &lt;li&gt;&lt;em&gt;ib&lt;/em&gt;&lt;/li&gt; 1531 - &lt;li&gt;&lt;em&gt;ba&lt;/em&gt;&lt;/li&gt; 1532 - &lt;li&gt;&lt;em&gt;sheut&lt;/em&gt;&lt;/li&gt; 1533 - &lt;/ul&gt; 1534 - &lt;p&gt;Egyptians emphasized on preserving the different parts of the soul. 1535 - Mummification for example, served to preserve the physical part of the 1536 - soul. The other components have their respective preservation 1537 - strategies.&lt;/p&gt; 1538 - &lt;p&gt;Of all of these bits, I find &lt;em&gt;ren&lt;/em&gt;, which simply means 1539 - &lt;em&gt;name&lt;/em&gt;, to be the most interesting. &lt;em&gt;Ba&lt;/em&gt;, the human-headed 1540 - chicken that represents &lt;em&gt;personality&lt;/em&gt;, is a close favourite.&lt;/p&gt; 1541 - &lt;p&gt;&lt;em&gt;Ren&lt;/em&gt; is the name given to a person at birth. Egyptians 1542 - believed that this portion of the soul would continue to live on for as 1543 - long as it was spoken. If you were someone worthy of continued 1544 - existence, your name would be inscribed all over the place. If you were 1545 - the type to snatch away bread from children, your name would be 1546 - condemned from memory, forgotten.&lt;/p&gt; 1547 - &lt;h3 id="garbage-collection"&gt;Garbage-collection&lt;/h3&gt; 1548 - &lt;p&gt;The concept of &lt;em&gt;ren&lt;/em&gt; seems to be perfectly analogous to 1549 - reference counted garbage-collection.&lt;/p&gt; 1550 - &lt;ul&gt; 1551 - &lt;li&gt;A name (&lt;em&gt;ren&lt;/em&gt;) is assigned to an object (person) on 1552 - initialization (at birth)&lt;/li&gt; 1553 - &lt;li&gt;Names are used to refer to objects&lt;/li&gt; 1554 - &lt;li&gt;Objects go out of existence when there are no more references to 1555 - them&lt;/li&gt; 1556 - &lt;/ul&gt; 1557 - &lt;p&gt;The concept of &lt;em&gt;ren&lt;/em&gt; seems to model human-memory. The 1558 - similarity with garbage-collection is now easily explained, because 1559 - garbage-collection models a program’s memory.&lt;/p&gt; 1560 - &lt;p&gt;Perhaps some cheeky Egyptian has attained immortality by creating a 1561 - &lt;em&gt;ren&lt;/em&gt;-cycle.&lt;/p&gt;</description> 1562 - <link>https://oppi.li/posts/a_reference_counted_afterlife/</link> 1563 - <pubDate>Tue, 02 Aug 2022 16:47:00 +0000</pubDate> 1564 - <guid>https://oppi.li/posts/a_reference_counted_afterlife/</guid> 1565 - </item> 1566 - <item> 1567 - <title>Lotus58</title> 1568 - <description>&lt;p&gt;Earlier this month, I decided that I would laugh at Indian customs in 1569 - the face by building a split-ergo mechanical keyboard from scratch 1570 - rather than purchasing a Moonlander.&lt;/p&gt; 1571 - &lt;figure&gt; 1572 - &lt;img src="https://u.peppe.rs/i8k.jpg" alt="The finished product" /&gt; 1573 - &lt;figcaption aria-hidden="true"&gt;The finished product&lt;/figcaption&gt; 1574 - &lt;/figure&gt; 1575 - &lt;h2 id="sourcing-the-parts"&gt;Sourcing the parts&lt;/h2&gt; 1576 - &lt;p&gt;If you, like me, live in India, you might find this section useful. 1577 - My approach to finding parts:&lt;/p&gt; 1578 - &lt;ul&gt; 1579 - &lt;li&gt;Check reputed, local online stores&lt;/li&gt; 1580 - &lt;li&gt;Check physical hardware stores&lt;/li&gt; 1581 - &lt;li&gt;Import the part&lt;/li&gt; 1582 - &lt;/ul&gt; 1583 - &lt;h3 id="pcbs"&gt;PCBs&lt;/h3&gt; 1584 - &lt;p&gt;This was by far the hardest component to procure. Fabrication 1585 - services have certain &lt;em&gt;capabilities&lt;/em&gt;. Capabilities are the 1586 - limitations of a fabrication service. For example, a service may be 1587 - capable of drilling holes no smaller than 0.3mm in diameter. Most sites 1588 - have a verification process to check if their capabilities meet your 1589 - design’s requirements. I tried a few local PCB fabrication services:&lt;/p&gt; 1590 - &lt;ul&gt; 1591 - &lt;li&gt;Lion PCB: Capabilities did not meet my requirements&lt;/li&gt; 1592 - &lt;li&gt;PCBPower: Capabilities did not meet my requirements&lt;/li&gt; 1593 - &lt;li&gt;Circuitwala: Capabilities did not meet my requirements&lt;/li&gt; 1594 - &lt;/ul&gt; 1595 - &lt;p&gt;I settled for JLCPCB, a Chinese service. PCBs themselves were 16 USD, 1596 - shipping was another 35 USD, and customs was another 3.5K INR 1597 - (ouch).&lt;/p&gt; 1598 - &lt;h3 id="case-material"&gt;Case material&lt;/h3&gt; 1599 - &lt;p&gt;I don’t really have a case for the Lotus58, it is more of a “plastic 1600 - sandwich”. I purchased acrylic plates from Robu. Cheap, fast, solid, and 1601 - customizable. I cannot recommend Robu enough. A full set of plates (2 1602 - top plates and 2 bottom plates) cost me about 500 INR. I also bought a 1603 - pair of laptop height raisers on Amazon to create a budget tenting 1604 - setup.&lt;/p&gt; 1605 - &lt;h3 id="electronics"&gt;Electronics&lt;/h3&gt; 1606 - &lt;p&gt;You’ll need a few rather specific electronic components such as 1607 - hotswap sockets and TRRS mounts, the rest are commonly available:&lt;/p&gt; 1608 - &lt;ul&gt; 1609 - &lt;li&gt;Hotswap sockets: StacksKB&lt;/li&gt; 1610 - &lt;li&gt;TRRS mounts (PJ 320A): StacksKB&lt;/li&gt; 1611 - &lt;li&gt;Diodes (1N4841): StacksKB&lt;/li&gt; 1612 - &lt;li&gt;M2 screws: StacksKB&lt;/li&gt; 1613 - &lt;li&gt;M2 spacers: ThinkRobotics&lt;/li&gt; 1614 - &lt;li&gt;Arduino Pro Micro: Robu&lt;/li&gt; 1615 - &lt;/ul&gt; 1616 - &lt;p&gt;I skimped out on optional components such as OLEDs and rotary 1617 - encoders.&lt;/p&gt; 1618 - &lt;h3 id="switches-and-keycaps"&gt;Switches and Keycaps&lt;/h3&gt; 1619 - &lt;p&gt;Arguably the most fun part of the build:&lt;/p&gt; 1620 - &lt;ul&gt; 1621 - &lt;li&gt;Gateron Oil King switches: Rectangles&lt;/li&gt; 1622 - &lt;li&gt;DSA blanks: Meckeys&lt;/li&gt; 1623 - &lt;/ul&gt; 1624 - &lt;h2 id="building-the-keyboard"&gt;Building the keyboard&lt;/h2&gt; 1625 - &lt;p&gt;The the build is extremely straightforward. Through hole components 1626 - are easy to solder. Be wary of component placement and orientation. 1627 - Check thrice, solder once. Few debugging tips:&lt;/p&gt; 1628 - &lt;ul&gt; 1629 - &lt;li&gt;if a single key does not actuate, check the hotswap for poor 1630 - soldering (reflow the joint), and the switch pins for deformation during 1631 - installation&lt;/li&gt; 1632 - &lt;li&gt;if an entire column or row activates on a single key-press, a 1633 - connection has been shorted&lt;/li&gt; 1634 - &lt;li&gt;if only some of the keys on a given row actuate, a diode has been 1635 - soldered on the wrong way&lt;/li&gt; 1636 - &lt;/ul&gt; 1637 - &lt;h2 id="the-typing-experience"&gt;The typing experience&lt;/h2&gt; 1638 - &lt;p&gt;I decidede to give QWERTY the boot and learn Colemak along with the 1639 - new keyboard. The first few weeks were terrible because I could neither 1640 - type QWERTY nor Colemak, but I got the hang of it pretty quickly. Typing 1641 - websites do help, but it is best to simply use it in your daily 1642 - workflow. No site can help you get accustomed to the various things you 1643 - use your keyboard for such as switching windows or navigating 1644 - vim/tmux.&lt;/p&gt; 1645 - &lt;h3 id="colemak"&gt;Colemak&lt;/h3&gt; 1646 - &lt;p&gt;Alt layouts such as Colemak are definitely worth it. I find that 1647 - Colemak reduces finger movement a lot, a good portion of the keys on the 1648 - left hand are the same as QWERTY, it is fairly easy to pick up as 1649 - well.&lt;/p&gt; 1650 - &lt;h3 id="vim"&gt;Vim&lt;/h3&gt; 1651 - &lt;p&gt;Using an alt layout means most programs with keyboard shortcuts are 1652 - not going to work as expected, &lt;code&gt;HJKL&lt;/code&gt; on vim for movements 1653 - being one of them. I took the short route out by creating a new layer 1654 - with arrow keys on the home row:&lt;/p&gt; 1655 - &lt;pre&gt;&lt;code&gt;default homerow: 1656 - H N E I 1657 - 1658 - &amp;quot;nav&amp;quot; layer: 1659 - &amp;lt; v ^ &amp;gt;&lt;/code&gt;&lt;/pre&gt; 1660 - &lt;p&gt;The remaining commands in vim are largely mnemonics. Navigating with 1661 - home-row arrow keys also means that I can use “HJKL” globally, to scroll 1662 - a website, for example.&lt;/p&gt; 1663 - &lt;h3 id="cutting-down-to-34-keys"&gt;Cutting down to 34 keys&lt;/h3&gt; 1664 - &lt;p&gt;A couple months into my ergo journey, I realized that I could get 1665 - away by moving my fingers even lesser. I moved modifiers such as 1666 - &lt;code&gt;Super&lt;/code&gt;, &lt;code&gt;Ctrl&lt;/code&gt;, &lt;code&gt;Alt&lt;/code&gt; and 1667 - &lt;code&gt;Shift&lt;/code&gt; to the home-row as QMK Mod Taps. The rest of the keys 1668 - are cleverly placed in layers, not too much unlike the Miryoku layout. 1669 - Even for someone that writes Rust (a symbol-heavy grammar), I find 1670 - 34-keys to be sufficient.&lt;/p&gt; 1671 - &lt;h2 id="conclusion"&gt;Conclusion&lt;/h2&gt; 1672 - &lt;p&gt;I have been bitten by the ergomech bug.&lt;/p&gt; 1673 - &lt;figure&gt; 1674 - &lt;img src="https://u.peppe.rs/XM3.jpg" alt="The Lotus58 in action" /&gt; 1675 - &lt;figcaption aria-hidden="true"&gt;The Lotus58 in action&lt;/figcaption&gt; 1676 - &lt;/figure&gt;</description> 1677 - <link>https://oppi.li/posts/lotus58/</link> 1678 - <pubDate>Mon, 13 Jun 2022 13:55:00 +0000</pubDate> 1679 - <guid>https://oppi.li/posts/lotus58/</guid> 1680 - </item> 1681 - <item> 1682 - <title>Lightweight Linting</title> 1683 - <description>&lt;p&gt;&lt;a 1684 - href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries"&gt;Tree-sitter&lt;/a&gt; 1685 - queries allow you to search for patterns in syntax trees, much like a 1686 - regex would, in text. Combine that with some Rust glue to write simple, 1687 - custom linters.&lt;/p&gt; 1688 - &lt;h3 id="tree-sitter-syntax-trees"&gt;Tree-sitter syntax trees&lt;/h3&gt; 1689 - &lt;p&gt;Here is a quick crash course on syntax trees generated by 1690 - tree-sitter. Syntax trees produced by tree-sitter are represented by 1691 - S-expressions. The generated S-expression for the following Rust 1692 - code,&lt;/p&gt; 1693 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre 1694 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; main() &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 1695 - &lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; x &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dv"&gt;2&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1696 - &lt;span id="cb1-3"&gt;&lt;a href="#cb1-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1697 - &lt;p&gt;would be:&lt;/p&gt; 1698 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 1699 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;(source_file&lt;/span&gt; 1700 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (function_item&lt;/span&gt; 1701 - &lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; name: (identifier)&lt;/span&gt; 1702 - &lt;span id="cb2-4"&gt;&lt;a href="#cb2-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; parameters: (parameters)&lt;/span&gt; 1703 - &lt;span id="cb2-5"&gt;&lt;a href="#cb2-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; body: &lt;/span&gt; 1704 - &lt;span id="cb2-6"&gt;&lt;a href="#cb2-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (block&lt;/span&gt; 1705 - &lt;span id="cb2-7"&gt;&lt;a href="#cb2-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (let_declaration &lt;/span&gt; 1706 - &lt;span id="cb2-8"&gt;&lt;a href="#cb2-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; pattern: (identifier)&lt;/span&gt; 1707 - &lt;span id="cb2-9"&gt;&lt;a href="#cb2-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; value: (integer_literal)))))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1708 - &lt;p&gt;Syntax trees generated by tree-sitter have a couple of other cool 1709 - properties: they are &lt;em&gt;lossless&lt;/em&gt; syntax trees. Given a lossless 1710 - syntax tree, you can regenerate the original source code in its 1711 - entirety. Consider the following addition to our example:&lt;/p&gt; 1712 - &lt;div class="sourceCode" id="cb3"&gt;&lt;pre 1713 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb3-1"&gt;&lt;a href="#cb3-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;fn&lt;/span&gt; main() &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 1714 - &lt;span id="cb3-2"&gt;&lt;a href="#cb3-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="co"&gt;// a comment goes here&lt;/span&gt;&lt;/span&gt; 1715 - &lt;span id="cb3-3"&gt;&lt;a href="#cb3-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; x &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dv"&gt;2&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1716 - &lt;span id="cb3-4"&gt;&lt;a href="#cb3-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1717 - &lt;p&gt;The tree-sitter syntax tree preserves the comment, while the typical 1718 - abstract syntax tree wouldn’t:&lt;/p&gt; 1719 - &lt;div class="sourceCode" id="cb4"&gt;&lt;pre 1720 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb4-1"&gt;&lt;a href="#cb4-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (source_file&lt;/span&gt; 1721 - &lt;span id="cb4-2"&gt;&lt;a href="#cb4-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (function_item&lt;/span&gt; 1722 - &lt;span id="cb4-3"&gt;&lt;a href="#cb4-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; name: (identifier)&lt;/span&gt; 1723 - &lt;span id="cb4-4"&gt;&lt;a href="#cb4-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; parameters: (parameters)&lt;/span&gt; 1724 - &lt;span id="cb4-5"&gt;&lt;a href="#cb4-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; body:&lt;/span&gt; 1725 - &lt;span id="cb4-6"&gt;&lt;a href="#cb4-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (block&lt;/span&gt; 1726 - &lt;span id="cb4-7"&gt;&lt;a href="#cb4-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; (line_comment)&lt;/span&gt; 1727 - &lt;span id="cb4-8"&gt;&lt;a href="#cb4-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (let_declaration&lt;/span&gt; 1728 - &lt;span id="cb4-9"&gt;&lt;a href="#cb4-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; pattern: (identifier)&lt;/span&gt; 1729 - &lt;span id="cb4-10"&gt;&lt;a href="#cb4-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; value: (integer_literal)))))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1730 - &lt;h3 id="tree-sitter-queries"&gt;Tree-sitter queries&lt;/h3&gt; 1731 - &lt;p&gt;Tree-sitter provides a DSL to match over CSTs. These queries resemble 1732 - our S-expression syntax trees, here is a query to match all line 1733 - comments in a Rust CST:&lt;/p&gt; 1734 - &lt;div class="sourceCode" id="cb5"&gt;&lt;pre 1735 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb5-1"&gt;&lt;a href="#cb5-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;(line_comment)&lt;/span&gt; 1736 - &lt;span id="cb5-2"&gt;&lt;a href="#cb5-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1737 - &lt;span id="cb5-3"&gt;&lt;a href="#cb5-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; matches the following rust code&lt;/span&gt;&lt;/span&gt; 1738 - &lt;span id="cb5-4"&gt;&lt;a href="#cb5-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; // a comment goes here&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1739 - &lt;p&gt;Neat, eh? But don’t take my word for it, give it a go on the &lt;a 1740 - href="https://tree-sitter.github.io/tree-sitter/playground"&gt;tree-sitter 1741 - playground&lt;/a&gt;. Type in a query like so:&lt;/p&gt; 1742 - &lt;div class="sourceCode" id="cb6"&gt;&lt;pre 1743 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb6-1"&gt;&lt;a href="#cb6-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; the web playground requires you to specify a &amp;quot;capture&amp;quot;&lt;/span&gt;&lt;/span&gt; 1744 - &lt;span id="cb6-2"&gt;&lt;a href="#cb6-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; you will notice the capture and the nodes it captured&lt;/span&gt;&lt;/span&gt; 1745 - &lt;span id="cb6-3"&gt;&lt;a href="#cb6-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; turn blue&lt;/span&gt;&lt;/span&gt; 1746 - &lt;span id="cb6-4"&gt;&lt;a href="#cb6-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;(line_comment) @capture&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1747 - &lt;p&gt;Here’s another to match &lt;code&gt;let&lt;/code&gt; expressions that bind an 1748 - integer to an identifier:&lt;/p&gt; 1749 - &lt;div class="sourceCode" id="cb7"&gt;&lt;pre 1750 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb7-1"&gt;&lt;a href="#cb7-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;(let_declaration&lt;/span&gt; 1751 - &lt;span id="cb7-2"&gt;&lt;a href="#cb7-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; pattern: (identifier)&lt;/span&gt; 1752 - &lt;span id="cb7-3"&gt;&lt;a href="#cb7-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; value: (integer_literal))&lt;/span&gt; 1753 - &lt;span id="cb7-4"&gt;&lt;a href="#cb7-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 1754 - &lt;span id="cb7-5"&gt;&lt;a href="#cb7-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; matches:&lt;/span&gt;&lt;/span&gt; 1755 - &lt;span id="cb7-6"&gt;&lt;a href="#cb7-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; let foo = 2;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1756 - &lt;p&gt;We can &lt;em&gt;capture&lt;/em&gt; nodes into variables:&lt;/p&gt; 1757 - &lt;div class="sourceCode" id="cb8"&gt;&lt;pre 1758 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb8-1"&gt;&lt;a href="#cb8-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;(let_declaration &lt;/span&gt; 1759 - &lt;span id="cb8-2"&gt;&lt;a href="#cb8-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; pattern: (identifier) @my-capture&lt;/span&gt; 1760 - &lt;span id="cb8-3"&gt;&lt;a href="#cb8-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; value: (integer_literal))&lt;/span&gt; 1761 - &lt;span id="cb8-4"&gt;&lt;a href="#cb8-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 1762 - &lt;span id="cb8-5"&gt;&lt;a href="#cb8-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; matches:&lt;/span&gt;&lt;/span&gt; 1763 - &lt;span id="cb8-6"&gt;&lt;a href="#cb8-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; let foo = 2;&lt;/span&gt;&lt;/span&gt; 1764 - &lt;span id="cb8-7"&gt;&lt;a href="#cb8-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1765 - &lt;span id="cb8-8"&gt;&lt;a href="#cb8-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; captures:&lt;/span&gt;&lt;/span&gt; 1766 - &lt;span id="cb8-9"&gt;&lt;a href="#cb8-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; foo&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1767 - &lt;p&gt;And apply certain &lt;em&gt;predicates&lt;/em&gt; to captures:&lt;/p&gt; 1768 - &lt;div class="sourceCode" id="cb9"&gt;&lt;pre 1769 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb9-1"&gt;&lt;a href="#cb9-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;((let_declaration&lt;/span&gt; 1770 - &lt;span id="cb9-2"&gt;&lt;a href="#cb9-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; pattern: (identifier) @my-capture&lt;/span&gt; 1771 - &lt;span id="cb9-3"&gt;&lt;a href="#cb9-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; value: (integer_literal))&lt;/span&gt; 1772 - &lt;span id="cb9-4"&gt;&lt;a href="#cb9-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (&lt;span class="sc"&gt;#e&lt;/span&gt;q? @my-capture &lt;span class="st"&gt;&amp;quot;foo&amp;quot;&lt;/span&gt;))&lt;/span&gt; 1773 - &lt;span id="cb9-5"&gt;&lt;a href="#cb9-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 1774 - &lt;span id="cb9-6"&gt;&lt;a href="#cb9-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; matches:&lt;/span&gt;&lt;/span&gt; 1775 - &lt;span id="cb9-7"&gt;&lt;a href="#cb9-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; let foo = 2;&lt;/span&gt;&lt;/span&gt; 1776 - &lt;span id="cb9-8"&gt;&lt;a href="#cb9-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1777 - &lt;span id="cb9-9"&gt;&lt;a href="#cb9-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; and not:&lt;/span&gt;&lt;/span&gt; 1778 - &lt;span id="cb9-10"&gt;&lt;a href="#cb9-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; let bar = 2;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1779 - &lt;p&gt;The &lt;code&gt;#match?&lt;/code&gt; predicate checks if a capture matches a 1780 - regex:&lt;/p&gt; 1781 - &lt;div class="sourceCode" id="cb10"&gt;&lt;pre 1782 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb10-1"&gt;&lt;a href="#cb10-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;((let_declaration&lt;/span&gt; 1783 - &lt;span id="cb10-2"&gt;&lt;a href="#cb10-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; pattern: (identifier) @my-capture&lt;/span&gt; 1784 - &lt;span id="cb10-3"&gt;&lt;a href="#cb10-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; value: (integer_literal))&lt;/span&gt; 1785 - &lt;span id="cb10-4"&gt;&lt;a href="#cb10-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (#match? @my-capture &lt;span class="st"&gt;&amp;quot;foo|bar&amp;quot;&lt;/span&gt;))&lt;/span&gt; 1786 - &lt;span id="cb10-5"&gt;&lt;a href="#cb10-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 1787 - &lt;span id="cb10-6"&gt;&lt;a href="#cb10-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; matches both `foo` and `bar`:&lt;/span&gt;&lt;/span&gt; 1788 - &lt;span id="cb10-7"&gt;&lt;a href="#cb10-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; let foo = 2;&lt;/span&gt;&lt;/span&gt; 1789 - &lt;span id="cb10-8"&gt;&lt;a href="#cb10-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; let bar = 2;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1790 - &lt;p&gt;Exhibit indifference, as a stoic programmer would, with the 1791 - &lt;em&gt;wildcard&lt;/em&gt; pattern:&lt;/p&gt; 1792 - &lt;div class="sourceCode" id="cb11"&gt;&lt;pre 1793 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb11-1"&gt;&lt;a href="#cb11-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;(let_declaration&lt;/span&gt; 1794 - &lt;span id="cb11-2"&gt;&lt;a href="#cb11-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; pattern: (identifier)&lt;/span&gt; 1795 - &lt;span id="cb11-3"&gt;&lt;a href="#cb11-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; value: (&lt;span class="op"&gt;_&lt;/span&gt;))&lt;/span&gt; 1796 - &lt;span id="cb11-4"&gt;&lt;a href="#cb11-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 1797 - &lt;span id="cb11-5"&gt;&lt;a href="#cb11-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; matches:&lt;/span&gt;&lt;/span&gt; 1798 - &lt;span id="cb11-6"&gt;&lt;a href="#cb11-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; let foo = &amp;quot;foo&amp;quot;;&lt;/span&gt;&lt;/span&gt; 1799 - &lt;span id="cb11-7"&gt;&lt;a href="#cb11-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; let foo = 42;&lt;/span&gt;&lt;/span&gt; 1800 - &lt;span id="cb11-8"&gt;&lt;a href="#cb11-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;; let foo = bar;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1801 - &lt;p&gt;&lt;a 1802 - href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries"&gt;The 1803 - documentation&lt;/a&gt; does the tree-sitter query DSL more justice, but we 1804 - now know enough to write our first lint.&lt;/p&gt; 1805 - &lt;h3 id="write-you-a-tree-sitter-lint"&gt;Write you a tree-sitter lint&lt;/h3&gt; 1806 - &lt;p&gt;Strings in &lt;code&gt;std::env&lt;/code&gt; functions are error prone:&lt;/p&gt; 1807 - &lt;div class="sourceCode" id="cb12"&gt;&lt;pre 1808 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb12-1"&gt;&lt;a href="#cb12-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="pp"&gt;std::env::&lt;/span&gt;remove_var(&lt;span class="st"&gt;&amp;quot;RUST_BACKTACE&amp;quot;&lt;/span&gt;)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1809 - &lt;span id="cb12-2"&gt;&lt;a href="#cb12-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="co"&gt;// ^^^^ &amp;quot;TACE&amp;quot; instead of &amp;quot;TRACE&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1810 - &lt;p&gt;I prefer this instead:&lt;/p&gt; 1811 - &lt;div class="sourceCode" id="cb13"&gt;&lt;pre 1812 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb13-1"&gt;&lt;a href="#cb13-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// somewhere in a module that is well spellchecked&lt;/span&gt;&lt;/span&gt; 1813 - &lt;span id="cb13-2"&gt;&lt;a href="#cb13-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;static&lt;/span&gt; BACKTRACE&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;&lt;span class="dt"&gt;str&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;RUST_BACKTRACE&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1814 - &lt;span id="cb13-3"&gt;&lt;a href="#cb13-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1815 - &lt;span id="cb13-4"&gt;&lt;a href="#cb13-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// rest of the codebase&lt;/span&gt;&lt;/span&gt; 1816 - &lt;span id="cb13-5"&gt;&lt;a href="#cb13-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="pp"&gt;std::env::&lt;/span&gt;remove_var(BACKTRACE)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1817 - &lt;p&gt;Let’s write a lint to find &lt;code&gt;std::env&lt;/code&gt; functions that use 1818 - strings. Put aside the effectiveness of this lint for the moment, and 1819 - take a stab at writing a tree-sitter query. For reference, a function 1820 - call like so:&lt;/p&gt; 1821 - &lt;div class="sourceCode" id="cb14"&gt;&lt;pre 1822 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb14-1"&gt;&lt;a href="#cb14-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;remove_var(&lt;span class="st"&gt;&amp;quot;RUST_BACKTRACE&amp;quot;&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1823 - &lt;p&gt;Produces the following S-expression:&lt;/p&gt; 1824 - &lt;div class="sourceCode" id="cb15"&gt;&lt;pre 1825 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb15-1"&gt;&lt;a href="#cb15-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;(call_expression&lt;/span&gt; 1826 - &lt;span id="cb15-2"&gt;&lt;a href="#cb15-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; function: (identifier)&lt;/span&gt; 1827 - &lt;span id="cb15-3"&gt;&lt;a href="#cb15-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; arguments: (arguments (string_literal)))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1828 - &lt;p&gt;We are definitely looking for a &lt;code&gt;call_expression&lt;/code&gt;:&lt;/p&gt; 1829 - &lt;div class="sourceCode" id="cb16"&gt;&lt;pre 1830 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb16-1"&gt;&lt;a href="#cb16-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;(call_expression) @raise&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1831 - &lt;p&gt;Whose function name matches &lt;code&gt;std::env::var&lt;/code&gt; or 1832 - &lt;code&gt;std::env::remove_var&lt;/code&gt; at the very least (I know, I know, 1833 - this isn’t the most optimal regex):&lt;/p&gt; 1834 - &lt;div class="sourceCode" id="cb17"&gt;&lt;pre 1835 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb17-1"&gt;&lt;a href="#cb17-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;((call_expression&lt;/span&gt; 1836 - &lt;span id="cb17-2"&gt;&lt;a href="#cb17-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; function: (&lt;span class="op"&gt;_&lt;/span&gt;) @fn-name) @raise&lt;/span&gt; 1837 - &lt;span id="cb17-3"&gt;&lt;a href="#cb17-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (#match? @fn-name &lt;span class="st"&gt;&amp;quot;std::env::(var|remove_var)&amp;quot;&lt;/span&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1838 - &lt;p&gt;Let’s turn that &lt;code&gt;std::&lt;/code&gt; prefix optional:&lt;/p&gt; 1839 - &lt;div class="sourceCode" id="cb18"&gt;&lt;pre 1840 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb18-1"&gt;&lt;a href="#cb18-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;((call_expression&lt;/span&gt; 1841 - &lt;span id="cb18-2"&gt;&lt;a href="#cb18-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; function: (&lt;span class="op"&gt;_&lt;/span&gt;) @fn-name) @raise&lt;/span&gt; 1842 - &lt;span id="cb18-3"&gt;&lt;a href="#cb18-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (#match? @fn-name &lt;span class="st"&gt;&amp;quot;(std::|)env::(var|remove_var)&amp;quot;&lt;/span&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1843 - &lt;p&gt;And ensure that &lt;code&gt;arguments&lt;/code&gt; is a string:&lt;/p&gt; 1844 - &lt;div class="sourceCode" id="cb19"&gt;&lt;pre 1845 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb19-1"&gt;&lt;a href="#cb19-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;((call_expression&lt;/span&gt; 1846 - &lt;span id="cb19-2"&gt;&lt;a href="#cb19-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; function: (&lt;span class="op"&gt;_&lt;/span&gt;) @fn-name&lt;/span&gt; 1847 - &lt;span id="cb19-3"&gt;&lt;a href="#cb19-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; arguments: (arguments (string_literal)))&lt;/span&gt; 1848 - &lt;span id="cb19-4"&gt;&lt;a href="#cb19-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (#match? @fn-name &lt;span class="st"&gt;&amp;quot;(std::|)env::(var|remove_var)&amp;quot;&lt;/span&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1849 - &lt;h3 id="running-our-linter"&gt;Running our linter&lt;/h3&gt; 1850 - &lt;p&gt;We could always plug our query into the web playground, but let’s go 1851 - a step further:&lt;/p&gt; 1852 - &lt;div class="sourceCode" id="cb20"&gt;&lt;pre 1853 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb20-1"&gt;&lt;a href="#cb20-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;cargo&lt;/span&gt; new &lt;span class="at"&gt;--bin&lt;/span&gt; toy-lint&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1854 - &lt;p&gt;Add &lt;code&gt;tree-sitter&lt;/code&gt; and &lt;code&gt;tree-sitter-rust&lt;/code&gt; to 1855 - your dependencies:&lt;/p&gt; 1856 - &lt;div class="sourceCode" id="cb21"&gt;&lt;pre 1857 - class="sourceCode toml"&gt;&lt;code class="sourceCode toml"&gt;&lt;span id="cb21-1"&gt;&lt;a href="#cb21-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# within Cargo.toml&lt;/span&gt;&lt;/span&gt; 1858 - &lt;span id="cb21-2"&gt;&lt;a href="#cb21-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;[dependencies]&lt;/span&gt;&lt;/span&gt; 1859 - &lt;span id="cb21-3"&gt;&lt;a href="#cb21-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;tree-sitter&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;0.20&amp;quot;&lt;/span&gt;&lt;/span&gt; 1860 - &lt;span id="cb21-4"&gt;&lt;a href="#cb21-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1861 - &lt;span id="cb21-5"&gt;&lt;a href="#cb21-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;[dependencies.tree-sitter-rust]&lt;/span&gt;&lt;/span&gt; 1862 - &lt;span id="cb21-6"&gt;&lt;a href="#cb21-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="dt"&gt;git&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;https://github.com/tree-sitter/tree-sitter-rust&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1863 - &lt;p&gt;Let’s load in some Rust code to work with. As &lt;a 1864 - href="https://en.wikipedia.org/wiki/Self-reference"&gt;an ode to Gödel&lt;/a&gt; 1865 - (G&lt;code&gt;ode&lt;/code&gt;l?), why not load in our linter itself:&lt;/p&gt; 1866 - &lt;div class="sourceCode" id="cb22"&gt;&lt;pre 1867 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb22-1"&gt;&lt;a href="#cb22-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; main() &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 1868 - &lt;span id="cb22-2"&gt;&lt;a href="#cb22-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; src &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;include_str!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;main.rs&amp;quot;&lt;/span&gt;)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1869 - &lt;span id="cb22-3"&gt;&lt;a href="#cb22-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1870 - &lt;p&gt;Most tree-sitter APIs require a reference to a &lt;code&gt;Language&lt;/code&gt; 1871 - struct, we will be working with Rust if you haven’t already guessed:&lt;/p&gt; 1872 - &lt;div class="sourceCode" id="cb23"&gt;&lt;pre 1873 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb23-1"&gt;&lt;a href="#cb23-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;tree_sitter::&lt;/span&gt;Language&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1874 - &lt;span id="cb23-2"&gt;&lt;a href="#cb23-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1875 - &lt;span id="cb23-3"&gt;&lt;a href="#cb23-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; rust_lang&lt;span class="op"&gt;:&lt;/span&gt; Language &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;tree_sitter_rust::&lt;/span&gt;language()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1876 - &lt;p&gt;Enough scaffolding, let’s parse some Rust:&lt;/p&gt; 1877 - &lt;div class="sourceCode" id="cb24"&gt;&lt;pre 1878 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb24-1"&gt;&lt;a href="#cb24-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;tree_sitter::&lt;/span&gt;Parser&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1879 - &lt;span id="cb24-2"&gt;&lt;a href="#cb24-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1880 - &lt;span id="cb24-3"&gt;&lt;a href="#cb24-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="kw"&gt;mut&lt;/span&gt; parser &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;Parser::&lt;/span&gt;new()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1881 - &lt;span id="cb24-4"&gt;&lt;a href="#cb24-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;parser&lt;span class="op"&gt;.&lt;/span&gt;set_language(rust_lang)&lt;span class="op"&gt;.&lt;/span&gt;unwrap()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1882 - &lt;span id="cb24-5"&gt;&lt;a href="#cb24-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1883 - &lt;span id="cb24-6"&gt;&lt;a href="#cb24-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; parse_tree &lt;span class="op"&gt;=&lt;/span&gt; parser&lt;span class="op"&gt;.&lt;/span&gt;parse(&lt;span class="op"&gt;&amp;amp;&lt;/span&gt;src&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="cn"&gt;None&lt;/span&gt;)&lt;span class="op"&gt;.&lt;/span&gt;unwrap()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1884 - &lt;p&gt;The second argument to &lt;code&gt;Parser::parse&lt;/code&gt; may be of interest. 1885 - Tree-sitter has this cool feature that allows for quick reparsing of 1886 - existing parse trees if they contain edits. If you do happen to want to 1887 - reparse a source file, you can pass in the old tree:&lt;/p&gt; 1888 - &lt;div class="sourceCode" id="cb25"&gt;&lt;pre 1889 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb25-1"&gt;&lt;a href="#cb25-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// if you wish to reparse instead of parse&lt;/span&gt;&lt;/span&gt; 1890 - &lt;span id="cb25-2"&gt;&lt;a href="#cb25-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;old_tree&lt;span class="op"&gt;.&lt;/span&gt;edit(&lt;span class="co"&gt;/* redacted */&lt;/span&gt;)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1891 - &lt;span id="cb25-3"&gt;&lt;a href="#cb25-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1892 - &lt;span id="cb25-4"&gt;&lt;a href="#cb25-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// generate shiny new reparsed tree&lt;/span&gt;&lt;/span&gt; 1893 - &lt;span id="cb25-5"&gt;&lt;a href="#cb25-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; new_tree &lt;span class="op"&gt;=&lt;/span&gt; parser&lt;span class="op"&gt;.&lt;/span&gt;parse(&lt;span class="op"&gt;&amp;amp;&lt;/span&gt;src&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="cn"&gt;Some&lt;/span&gt;(old_tree))&lt;span class="op"&gt;.&lt;/span&gt;unwrap()&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1894 - &lt;p&gt;Anyhow (&lt;a href="http://github.com/dtolnay/anyhow"&gt;hah!&lt;/a&gt;), now 1895 - that we have a parse tree, we can inspect it:&lt;/p&gt; 1896 - &lt;div class="sourceCode" id="cb26"&gt;&lt;pre 1897 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb26-1"&gt;&lt;a href="#cb26-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="pp"&gt;println!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;{}&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; parse_tree&lt;span class="op"&gt;.&lt;/span&gt;root_node()&lt;span class="op"&gt;.&lt;/span&gt;to_sexp())&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1898 - &lt;p&gt;Or better yet, run a query on it:&lt;/p&gt; 1899 - &lt;div class="sourceCode" id="cb27"&gt;&lt;pre 1900 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb27-1"&gt;&lt;a href="#cb27-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;tree_sitter::&lt;/span&gt;Query&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1901 - &lt;span id="cb27-2"&gt;&lt;a href="#cb27-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1902 - &lt;span id="cb27-3"&gt;&lt;a href="#cb27-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; query &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;Query::&lt;/span&gt;new(&lt;/span&gt; 1903 - &lt;span id="cb27-4"&gt;&lt;a href="#cb27-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; rust_lang&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 1904 - &lt;span id="cb27-5"&gt;&lt;a href="#cb27-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="st"&gt;r#&amp;quot;&lt;/span&gt;&lt;/span&gt; 1905 - &lt;span id="cb27-6"&gt;&lt;a href="#cb27-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt; ((call_expression&lt;/span&gt;&lt;/span&gt; 1906 - &lt;span id="cb27-7"&gt;&lt;a href="#cb27-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt; function: (_) @fn-name&lt;/span&gt;&lt;/span&gt; 1907 - &lt;span id="cb27-8"&gt;&lt;a href="#cb27-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt; arguments: (arguments (string_literal))) @raise&lt;/span&gt;&lt;/span&gt; 1908 - &lt;span id="cb27-9"&gt;&lt;a href="#cb27-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt; (#match? @fn-name &amp;quot;(std::|)env::(var|remove_var)&amp;quot;))&lt;/span&gt;&lt;/span&gt; 1909 - &lt;span id="cb27-10"&gt;&lt;a href="#cb27-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="st"&gt; &amp;quot;#&lt;/span&gt;&lt;/span&gt; 1910 - &lt;span id="cb27-11"&gt;&lt;a href="#cb27-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;)&lt;/span&gt; 1911 - &lt;span id="cb27-12"&gt;&lt;a href="#cb27-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;.&lt;/span&gt;unwrap()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1912 - &lt;p&gt;A &lt;code&gt;QueryCursor&lt;/code&gt; is tree-sitter’s way of maintaining state 1913 - as we iterate through the matches or captures produced by running a 1914 - query on the parse tree. Observe:&lt;/p&gt; 1915 - &lt;div class="sourceCode" id="cb28"&gt;&lt;pre 1916 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb28-1"&gt;&lt;a href="#cb28-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;tree_sitter::&lt;/span&gt;QueryCursor&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1917 - &lt;span id="cb28-2"&gt;&lt;a href="#cb28-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1918 - &lt;span id="cb28-3"&gt;&lt;a href="#cb28-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="kw"&gt;mut&lt;/span&gt; query_cursor &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;QueryCursor::&lt;/span&gt;new()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1919 - &lt;span id="cb28-4"&gt;&lt;a href="#cb28-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; all_matches &lt;span class="op"&gt;=&lt;/span&gt; query_cursor&lt;span class="op"&gt;.&lt;/span&gt;matches(&lt;/span&gt; 1920 - &lt;span id="cb28-5"&gt;&lt;a href="#cb28-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;query&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 1921 - &lt;span id="cb28-6"&gt;&lt;a href="#cb28-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; parse_tree&lt;span class="op"&gt;.&lt;/span&gt;root_node()&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 1922 - &lt;span id="cb28-7"&gt;&lt;a href="#cb28-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; src&lt;span class="op"&gt;.&lt;/span&gt;as_bytes()&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 1923 - &lt;span id="cb28-8"&gt;&lt;a href="#cb28-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1924 - &lt;p&gt;We begin by passing our query to the cursor, followed by the “root 1925 - node”, which is another way of saying, “start from the top”, and lastly, 1926 - the source itself. If you have already taken a look at the C API, you 1927 - will notice that the last argument, the source (known as the 1928 - &lt;code&gt;TextProvider&lt;/code&gt;), is not required. The Rust bindings seem to 1929 - require this argument to provide predicate functionality such as 1930 - &lt;code&gt;#match?&lt;/code&gt; and &lt;code&gt;#eq?&lt;/code&gt;.&lt;/p&gt; 1931 - &lt;p&gt;Do something with the matches:&lt;/p&gt; 1932 - &lt;div class="sourceCode" id="cb29"&gt;&lt;pre 1933 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb29-1"&gt;&lt;a href="#cb29-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// get the index of the capture named &amp;quot;raise&amp;quot;&lt;/span&gt;&lt;/span&gt; 1934 - &lt;span id="cb29-2"&gt;&lt;a href="#cb29-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; raise_idx &lt;span class="op"&gt;=&lt;/span&gt; query&lt;span class="op"&gt;.&lt;/span&gt;capture_index_for_name(&lt;span class="st"&gt;&amp;quot;raise&amp;quot;&lt;/span&gt;)&lt;span class="op"&gt;.&lt;/span&gt;unwrap()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1935 - &lt;span id="cb29-3"&gt;&lt;a href="#cb29-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 1936 - &lt;span id="cb29-4"&gt;&lt;a href="#cb29-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;for&lt;/span&gt; each_match &lt;span class="kw"&gt;in&lt;/span&gt; all_matches &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 1937 - &lt;span id="cb29-5"&gt;&lt;a href="#cb29-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="co"&gt;// iterate over all captures called &amp;quot;raise&amp;quot;&lt;/span&gt;&lt;/span&gt; 1938 - &lt;span id="cb29-6"&gt;&lt;a href="#cb29-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="co"&gt;// ignore captures such as &amp;quot;fn-name&amp;quot;&lt;/span&gt;&lt;/span&gt; 1939 - &lt;span id="cb29-7"&gt;&lt;a href="#cb29-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;for&lt;/span&gt; capture &lt;span class="kw"&gt;in&lt;/span&gt; each_match&lt;/span&gt; 1940 - &lt;span id="cb29-8"&gt;&lt;a href="#cb29-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;.&lt;/span&gt;captures&lt;/span&gt; 1941 - &lt;span id="cb29-9"&gt;&lt;a href="#cb29-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;.&lt;/span&gt;iter()&lt;/span&gt; 1942 - &lt;span id="cb29-10"&gt;&lt;a href="#cb29-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;.&lt;/span&gt;filter(&lt;span class="op"&gt;|&lt;/span&gt;c&lt;span class="op"&gt;|&lt;/span&gt; c&lt;span class="op"&gt;.&lt;/span&gt;idx &lt;span class="op"&gt;==&lt;/span&gt; raise_idx)&lt;/span&gt; 1943 - &lt;span id="cb29-11"&gt;&lt;a href="#cb29-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 1944 - &lt;span id="cb29-12"&gt;&lt;a href="#cb29-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; range &lt;span class="op"&gt;=&lt;/span&gt; capture&lt;span class="op"&gt;.&lt;/span&gt;node&lt;span class="op"&gt;.&lt;/span&gt;range()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1945 - &lt;span id="cb29-13"&gt;&lt;a href="#cb29-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; text &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;src[range&lt;span class="op"&gt;.&lt;/span&gt;start_byte&lt;span class="op"&gt;..&lt;/span&gt;range&lt;span class="op"&gt;.&lt;/span&gt;end_byte]&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1946 - &lt;span id="cb29-14"&gt;&lt;a href="#cb29-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; line &lt;span class="op"&gt;=&lt;/span&gt; range&lt;span class="op"&gt;.&lt;/span&gt;start_point&lt;span class="op"&gt;.&lt;/span&gt;row&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1947 - &lt;span id="cb29-15"&gt;&lt;a href="#cb29-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; col &lt;span class="op"&gt;=&lt;/span&gt; range&lt;span class="op"&gt;.&lt;/span&gt;start_point&lt;span class="op"&gt;.&lt;/span&gt;column&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1948 - &lt;span id="cb29-16"&gt;&lt;a href="#cb29-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;println!&lt;/span&gt;(&lt;/span&gt; 1949 - &lt;span id="cb29-17"&gt;&lt;a href="#cb29-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="st"&gt;&amp;quot;[Line: {}, Col: {}] Offending source code: `{}`&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 1950 - &lt;span id="cb29-18"&gt;&lt;a href="#cb29-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; line&lt;span class="op"&gt;,&lt;/span&gt; col&lt;span class="op"&gt;,&lt;/span&gt; text&lt;/span&gt; 1951 - &lt;span id="cb29-19"&gt;&lt;a href="#cb29-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; )&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 1952 - &lt;span id="cb29-20"&gt;&lt;a href="#cb29-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 1953 - &lt;span id="cb29-21"&gt;&lt;a href="#cb29-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1954 - &lt;p&gt;Lastly, add the following line to your source code, to get the linter 1955 - to catch something:&lt;/p&gt; 1956 - &lt;div class="sourceCode" id="cb30"&gt;&lt;pre 1957 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb30-1"&gt;&lt;a href="#cb30-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="pp"&gt;env::&lt;/span&gt;remove_var(&lt;span class="st"&gt;&amp;quot;RUST_BACKTRACE&amp;quot;&lt;/span&gt;)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1958 - &lt;p&gt;And &lt;code&gt;cargo run&lt;/code&gt;:&lt;/p&gt; 1959 - &lt;pre class="shell"&gt;&lt;code&gt;λ cargo run 1960 - Compiling toy-lint v0.1.0 (/redacted/path/to/toy-lint) 1961 - Finished dev [unoptimized + debuginfo] target(s) in 0.74s 1962 - Running `target/debug/toy-lint` 1963 - [Line: 40, Col: 4] Offending source code: `env::remove_var(&amp;quot;RUST_BACKTRACE&amp;quot;)`&lt;/code&gt;&lt;/pre&gt; 1964 - &lt;p&gt;Thank you tree-sitter!&lt;/p&gt; 1965 - &lt;h3 id="bonus"&gt;Bonus&lt;/h3&gt; 1966 - &lt;p&gt;Keen readers will notice that I avoided 1967 - &lt;code&gt;std::env::set_var&lt;/code&gt;. Because &lt;code&gt;set_var&lt;/code&gt; is called 1968 - with two arguments, a “key” and a “value”, unlike &lt;code&gt;env::var&lt;/code&gt; 1969 - and &lt;code&gt;env::remove_var&lt;/code&gt;. As a result, it requires more 1970 - juggling:&lt;/p&gt; 1971 - &lt;div class="sourceCode" id="cb32"&gt;&lt;pre 1972 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb32-1"&gt;&lt;a href="#cb32-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;((call_expression&lt;/span&gt; 1973 - &lt;span id="cb32-2"&gt;&lt;a href="#cb32-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; function: (&lt;span class="op"&gt;_&lt;/span&gt;) @fn-name&lt;/span&gt; 1974 - &lt;span id="cb32-3"&gt;&lt;a href="#cb32-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; arguments: (arguments &lt;span class="op"&gt;.&lt;/span&gt; (string_literal)&lt;span class="op"&gt;?&lt;/span&gt; &lt;span class="op"&gt;.&lt;/span&gt; (string_literal) &lt;span class="op"&gt;.&lt;/span&gt;)) @raise&lt;/span&gt; 1975 - &lt;span id="cb32-4"&gt;&lt;a href="#cb32-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (#match? @fn-name &lt;span class="st"&gt;&amp;quot;(std::|)env::(var|remove_var|set_var)&amp;quot;&lt;/span&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1976 - &lt;p&gt;The interesting part of this query is the humble &lt;code&gt;.&lt;/code&gt;, the 1977 - &lt;em&gt;anchor&lt;/em&gt; operator. Anchors help constrain child nodes in certain 1978 - ways. In this case, it ensures that we match exactly two 1979 - &lt;code&gt;string_literal&lt;/code&gt;s who are siblings or exactly one 1980 - &lt;code&gt;string_literal&lt;/code&gt; with no siblings. Unfortunately, this query 1981 - also matches the following invalid Rust code:&lt;/p&gt; 1982 - &lt;div class="sourceCode" id="cb33"&gt;&lt;pre 1983 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb33-1"&gt;&lt;a href="#cb33-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// remove_var accepts only 1 arg!&lt;/span&gt;&lt;/span&gt; 1984 - &lt;span id="cb33-2"&gt;&lt;a href="#cb33-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="pp"&gt;std::env::&lt;/span&gt;remove_var(&lt;span class="st"&gt;&amp;quot;RUST_BACKTRACE&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;1&amp;quot;&lt;/span&gt;)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 1985 - &lt;h3 id="notes"&gt;Notes&lt;/h3&gt; 1986 - &lt;p&gt;All-in-all, the query DSL does a great job in lowering the bar to 1987 - writing language tools. The knowledge gained from mastering the query 1988 - DSL can be applied to other languages that have tree-sitter grammars 1989 - too. This query detects &lt;code&gt;to_json&lt;/code&gt; methods that do not accept 1990 - additional arguments, in Ruby:&lt;/p&gt; 1991 - &lt;div class="sourceCode" id="cb34"&gt;&lt;pre 1992 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb34-1"&gt;&lt;a href="#cb34-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;((method&lt;/span&gt; 1993 - &lt;span id="cb34-2"&gt;&lt;a href="#cb34-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; name: (identifier) @fn&lt;/span&gt; 1994 - &lt;span id="cb34-3"&gt;&lt;a href="#cb34-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; !parameters)&lt;/span&gt; 1995 - &lt;span id="cb34-4"&gt;&lt;a href="#cb34-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (&lt;span class="sc"&gt;#i&lt;/span&gt;s? @fn &lt;span class="st"&gt;&amp;quot;to_json&amp;quot;&lt;/span&gt;))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description> 1996 - <link>https://oppi.li/posts/lightweight_linting/</link> 1997 - <pubDate>Wed, 26 Jan 2022 12:52:00 +0000</pubDate> 1998 - <guid>https://oppi.li/posts/lightweight_linting/</guid> 1999 - </item> 2000 - <item> 2001 - <title>Novice Nix: Flake Templates</title> 2002 - <description>&lt;p&gt;Flakes are very handy to setup entirely pure, project-specific 2003 - dependencies (not just dependencies, but build steps, shell environments 2004 - and more) in a declarative way. Writing Flake expressions can get 2005 - repetitive though, oftentimes, you’d much rather start off with a 2006 - skeleton. Luckily, &lt;code&gt;nix&lt;/code&gt; already supports templates!&lt;/p&gt; 2007 - &lt;p&gt;You might already be familiar with &lt;code&gt;nix flake init&lt;/code&gt;, that 2008 - drops a “default” flake expression into your current working directory. 2009 - If you head over to the manpage:&lt;/p&gt; 2010 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre 2011 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;nix&lt;/span&gt; flake init &lt;span class="at"&gt;--help&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2012 - &lt;p&gt;You will read that &lt;code&gt;nix flake init&lt;/code&gt; creates a flake using 2013 - the “default template”. Additionally, you can create a flake from a 2014 - specific template by passing the &lt;code&gt;-t&lt;/code&gt; flag. Where does this 2015 - default originate from?&lt;/p&gt; 2016 - &lt;h2 id="flake-registries"&gt;Flake Registries&lt;/h2&gt; 2017 - &lt;p&gt;Quick detour into registries! Registries are a way to alias popular 2018 - flakes using identifiers:&lt;/p&gt; 2019 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 2020 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# list a few predefined registries&lt;/span&gt;&lt;/span&gt; 2021 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix registry list&lt;/span&gt; 2022 - &lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;.&lt;/span&gt; . . &lt;/span&gt; 2023 - &lt;span id="cb2-4"&gt;&lt;a href="#cb2-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;global&lt;/span&gt; flake:nixpkgs github:NixOS/nixpkgs&lt;/span&gt; 2024 - &lt;span id="cb2-5"&gt;&lt;a href="#cb2-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;global&lt;/span&gt; flake:patchelf github:NixOS/patchelf&lt;/span&gt; 2025 - &lt;span id="cb2-6"&gt;&lt;a href="#cb2-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;global&lt;/span&gt; flake:nix-serve github:edolstra/nix-serve&lt;/span&gt; 2026 - &lt;span id="cb2-7"&gt;&lt;a href="#cb2-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;global&lt;/span&gt; flake:templates github:NixOS/templates&lt;/span&gt; 2027 - &lt;span id="cb2-8"&gt;&lt;a href="#cb2-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;global&lt;/span&gt; flake:nickel github:tweag/nickel&lt;/span&gt; 2028 - &lt;span id="cb2-9"&gt;&lt;a href="#cb2-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;.&lt;/span&gt; . .&lt;/span&gt; 2029 - &lt;span id="cb2-10"&gt;&lt;a href="#cb2-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2030 - &lt;span id="cb2-11"&gt;&lt;a href="#cb2-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# you can do &lt;/span&gt;&lt;/span&gt; 2031 - &lt;span id="cb2-12"&gt;&lt;a href="#cb2-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake show nickel&lt;/span&gt; 2032 - &lt;span id="cb2-13"&gt;&lt;a href="#cb2-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2033 - &lt;span id="cb2-14"&gt;&lt;a href="#cb2-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# instead of &lt;/span&gt;&lt;/span&gt; 2034 - &lt;span id="cb2-15"&gt;&lt;a href="#cb2-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake show github:tweag/nickel&lt;/span&gt; 2035 - &lt;span id="cb2-16"&gt;&lt;a href="#cb2-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2036 - &lt;span id="cb2-17"&gt;&lt;a href="#cb2-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# which is short for&lt;/span&gt;&lt;/span&gt; 2037 - &lt;span id="cb2-18"&gt;&lt;a href="#cb2-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake show git+https://github.com/tweag/nickel&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2038 - &lt;p&gt;You might notice a registry called &lt;code&gt;templates&lt;/code&gt; aliased to 2039 - &lt;code&gt;github:NixOS/templates&lt;/code&gt;. Take a peek with 2040 - &lt;code&gt;nix flake show&lt;/code&gt;:&lt;/p&gt; 2041 - &lt;div class="sourceCode" id="cb3"&gt;&lt;pre 2042 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb3-1"&gt;&lt;a href="#cb3-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake show templates&lt;/span&gt; 2043 - &lt;span id="cb3-2"&gt;&lt;a href="#cb3-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;github:NixOS/templates/79f48a7b822f35c068c5e235da2e9fbd154cecee&lt;/span&gt;&lt;/span&gt; 2044 - &lt;span id="cb3-3"&gt;&lt;a href="#cb3-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;├───defaultTemplate:&lt;/span&gt; template: A very basic flake&lt;/span&gt; 2045 - &lt;span id="cb3-4"&gt;&lt;a href="#cb3-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;└───templates&lt;/span&gt;&lt;/span&gt; 2046 - &lt;span id="cb3-5"&gt;&lt;a href="#cb3-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;├───bash-hello:&lt;/span&gt; template: An over-engineered Hello World in bash&lt;/span&gt; 2047 - &lt;span id="cb3-6"&gt;&lt;a href="#cb3-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;├───c-hello:&lt;/span&gt; template: An over-engineered Hello World in C&lt;/span&gt; 2048 - &lt;span id="cb3-7"&gt;&lt;a href="#cb3-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;├───rust-web-server:&lt;/span&gt; template: A Rust web server including a NixOS module&lt;/span&gt; 2049 - &lt;span id="cb3-8"&gt;&lt;a href="#cb3-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;├───simpleContainer:&lt;/span&gt; template: A NixOS container running apache-httpd&lt;/span&gt; 2050 - &lt;span id="cb3-9"&gt;&lt;a href="#cb3-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;└───trivial:&lt;/span&gt; template: A very basic flake&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2051 - &lt;p&gt;Aha! There is a flake output called &lt;code&gt;defaultTemplate&lt;/code&gt;. 2052 - This is the template being sourced when you run 2053 - &lt;code&gt;nix flake init&lt;/code&gt;. Astute readers may conclude the 2054 - following:&lt;/p&gt; 2055 - &lt;div class="sourceCode" id="cb4"&gt;&lt;pre 2056 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb4-1"&gt;&lt;a href="#cb4-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake init&lt;/span&gt; 2057 - &lt;span id="cb4-2"&gt;&lt;a href="#cb4-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2058 - &lt;span id="cb4-3"&gt;&lt;a href="#cb4-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# is equivalent to&lt;/span&gt;&lt;/span&gt; 2059 - &lt;span id="cb4-4"&gt;&lt;a href="#cb4-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake init &lt;span class="at"&gt;-t&lt;/span&gt; templates#defaultTemplate&lt;/span&gt; 2060 - &lt;span id="cb4-5"&gt;&lt;a href="#cb4-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2061 - &lt;span id="cb4-6"&gt;&lt;a href="#cb4-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# is equivalent to&lt;/span&gt;&lt;/span&gt; 2062 - &lt;span id="cb4-7"&gt;&lt;a href="#cb4-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake init &lt;span class="at"&gt;-t&lt;/span&gt; github:NixOS/templates#defaultTemplate&lt;/span&gt; 2063 - &lt;span id="cb4-8"&gt;&lt;a href="#cb4-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2064 - &lt;span id="cb4-9"&gt;&lt;a href="#cb4-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# which is short for&lt;/span&gt;&lt;/span&gt; 2065 - &lt;span id="cb4-10"&gt;&lt;a href="#cb4-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake init &lt;span class="at"&gt;-t&lt;/span&gt; git+https://github.com/NixOS/templates#defaultTemplate&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2066 - &lt;p&gt;Similarly, the other templates can be accessed via:&lt;/p&gt; 2067 - &lt;div class="sourceCode" id="cb5"&gt;&lt;pre 2068 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb5-1"&gt;&lt;a href="#cb5-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake init &lt;span class="at"&gt;-t&lt;/span&gt; templates#c-hello&lt;/span&gt; 2069 - &lt;span id="cb5-2"&gt;&lt;a href="#cb5-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake init &lt;span class="at"&gt;-t&lt;/span&gt; templates#simpleContainer&lt;/span&gt; 2070 - &lt;span id="cb5-3"&gt;&lt;a href="#cb5-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# I think you get the drift ...&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2071 - &lt;h2 id="rolling-your-own-templates"&gt;Rolling your own templates&lt;/h2&gt; 2072 - &lt;p&gt;Alright, so all we need to do is:&lt;/p&gt; 2073 - &lt;ul&gt; 2074 - &lt;li&gt;create a flake with a &lt;code&gt;templates&lt;/code&gt; output&lt;/li&gt; 2075 - &lt;li&gt;populate our template directories with content&lt;/li&gt; 2076 - &lt;li&gt;(&lt;strong&gt;optionally&lt;/strong&gt;) alias our custom templates flake to an 2077 - identifier using registries, for easier access&lt;/li&gt; 2078 - &lt;/ul&gt; 2079 - &lt;p&gt;Start off by creating a directory to store your templates in (we will 2080 - be converting this to a registry later):&lt;/p&gt; 2081 - &lt;div class="sourceCode" id="cb6"&gt;&lt;pre 2082 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb6-1"&gt;&lt;a href="#cb6-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; mkdir ~/mytemplates&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2083 - &lt;p&gt;A flake that exposes a “template” as its output looks something like 2084 - this:&lt;/p&gt; 2085 - &lt;div class="sourceCode" id="cb7"&gt;&lt;pre 2086 - class="sourceCode nix"&gt;&lt;code class="sourceCode nix"&gt;&lt;span id="cb7-1"&gt;&lt;a href="#cb7-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# inside ~/mytemplates/flake.nix&lt;/span&gt;&lt;/span&gt; 2087 - &lt;span id="cb7-2"&gt;&lt;a href="#cb7-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2088 - &lt;span id="cb7-3"&gt;&lt;a href="#cb7-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2089 - &lt;span id="cb7-4"&gt;&lt;a href="#cb7-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;description&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Pepper&amp;#39;s flake templates&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2090 - &lt;span id="cb7-5"&gt;&lt;a href="#cb7-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2091 - &lt;span id="cb7-6"&gt;&lt;a href="#cb7-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;outputs&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="va"&gt;self&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="op"&gt;...&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;: &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2092 - &lt;span id="cb7-7"&gt;&lt;a href="#cb7-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;templates&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2093 - &lt;span id="cb7-8"&gt;&lt;a href="#cb7-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;latex-report&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2094 - &lt;span id="cb7-9"&gt;&lt;a href="#cb7-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;path&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;./latex-report-template&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2095 - &lt;span id="cb7-10"&gt;&lt;a href="#cb7-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;description&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;A latex whitepaper project&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2096 - &lt;span id="cb7-11"&gt;&lt;a href="#cb7-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 2097 - &lt;span id="cb7-12"&gt;&lt;a href="#cb7-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;rust-hello&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2098 - &lt;span id="cb7-13"&gt;&lt;a href="#cb7-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;path&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;./rust-hello-template&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2099 - &lt;span id="cb7-14"&gt;&lt;a href="#cb7-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;description&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Simple Hello World in Rust&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2100 - &lt;span id="cb7-15"&gt;&lt;a href="#cb7-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 2101 - &lt;span id="cb7-16"&gt;&lt;a href="#cb7-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 2102 - &lt;span id="cb7-17"&gt;&lt;a href="#cb7-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 2103 - &lt;span id="cb7-18"&gt;&lt;a href="#cb7-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2104 - &lt;p&gt;The &lt;code&gt;path&lt;/code&gt; attribute to each template is what gets copied 2105 - over when you initialize a flake. Running 2106 - &lt;code&gt;nix flake init -t .#latex-report&lt;/code&gt; will initialize the 2107 - current directory with the contents of 2108 - &lt;code&gt;./latex-report-template&lt;/code&gt; (we are yet to populate these 2109 - directories).&lt;/p&gt; 2110 - &lt;p&gt;The output of &lt;code&gt;nix flake show&lt;/code&gt; should be something 2111 - like:&lt;/p&gt; 2112 - &lt;div class="sourceCode" id="cb8"&gt;&lt;pre 2113 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb8-1"&gt;&lt;a href="#cb8-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake show&lt;/span&gt; 2114 - &lt;span id="cb8-2"&gt;&lt;a href="#cb8-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;path:/home/np/code/nix-stuff/template-tests?narHash=sha256-{...}&lt;/span&gt;&lt;/span&gt; 2115 - &lt;span id="cb8-3"&gt;&lt;a href="#cb8-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;└───templates&lt;/span&gt;&lt;/span&gt; 2116 - &lt;span id="cb8-4"&gt;&lt;a href="#cb8-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;├───latex-report:&lt;/span&gt; template: A latex whitepaper project&lt;/span&gt; 2117 - &lt;span id="cb8-5"&gt;&lt;a href="#cb8-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;└───rust-hello:&lt;/span&gt; template: Simple Hello World in Rust&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2118 - &lt;p&gt;Populate your template directories with content, here are my template 2119 - directories for example:&lt;/p&gt; 2120 - &lt;div class="sourceCode" id="cb9"&gt;&lt;pre 2121 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb9-1"&gt;&lt;a href="#cb9-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; tree mytemplates&lt;/span&gt; 2122 - &lt;span id="cb9-2"&gt;&lt;a href="#cb9-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;mytemplates/&lt;/span&gt;&lt;/span&gt; 2123 - &lt;span id="cb9-3"&gt;&lt;a href="#cb9-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;├──&lt;/span&gt; flake.nix&lt;/span&gt; 2124 - &lt;span id="cb9-4"&gt;&lt;a href="#cb9-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;├──&lt;/span&gt; latex-report-template&lt;/span&gt; 2125 - &lt;span id="cb9-5"&gt;&lt;a href="#cb9-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│  &lt;/span&gt; ├── flake.nix&lt;/span&gt; 2126 - &lt;span id="cb9-6"&gt;&lt;a href="#cb9-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│  &lt;/span&gt; ├── makefile&lt;/span&gt; 2127 - &lt;span id="cb9-7"&gt;&lt;a href="#cb9-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│  &lt;/span&gt; └── src&lt;/span&gt; 2128 - &lt;span id="cb9-8"&gt;&lt;a href="#cb9-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│  &lt;/span&gt; ├── meta.sty&lt;/span&gt; 2129 - &lt;span id="cb9-9"&gt;&lt;a href="#cb9-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;│  &lt;/span&gt; └── report.tex&lt;/span&gt; 2130 - &lt;span id="cb9-10"&gt;&lt;a href="#cb9-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;└──&lt;/span&gt; rust-hello-template&lt;/span&gt; 2131 - &lt;span id="cb9-11"&gt;&lt;a href="#cb9-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;├──&lt;/span&gt; Cargo.toml&lt;/span&gt; 2132 - &lt;span id="cb9-12"&gt;&lt;a href="#cb9-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;├──&lt;/span&gt; flake.nix&lt;/span&gt; 2133 - &lt;span id="cb9-13"&gt;&lt;a href="#cb9-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;└──&lt;/span&gt; src&lt;/span&gt; 2134 - &lt;span id="cb9-14"&gt;&lt;a href="#cb9-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;└──&lt;/span&gt; main.rs&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2135 - &lt;p&gt;And that’s it! Start using your templates with:&lt;/p&gt; 2136 - &lt;div class="sourceCode" id="cb10"&gt;&lt;pre 2137 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb10-1"&gt;&lt;a href="#cb10-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake init &lt;span class="at"&gt;-t&lt;/span&gt; ~/mytemplates#rust-hello&lt;/span&gt; 2138 - &lt;span id="cb10-2"&gt;&lt;a href="#cb10-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; tree .&lt;/span&gt; 2139 - &lt;span id="cb10-3"&gt;&lt;a href="#cb10-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;.&lt;/span&gt;&lt;/span&gt; 2140 - &lt;span id="cb10-4"&gt;&lt;a href="#cb10-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;├──&lt;/span&gt; Cargo.toml&lt;/span&gt; 2141 - &lt;span id="cb10-5"&gt;&lt;a href="#cb10-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;├──&lt;/span&gt; flake.nix&lt;/span&gt; 2142 - &lt;span id="cb10-6"&gt;&lt;a href="#cb10-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;└──&lt;/span&gt; src&lt;/span&gt; 2143 - &lt;span id="cb10-7"&gt;&lt;a href="#cb10-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="ex"&gt;└──&lt;/span&gt; main.rs&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2144 - &lt;p&gt;To avoid writing &lt;code&gt;~/mytemplates&lt;/code&gt; each time, simply alias 2145 - it to a registry:&lt;/p&gt; 2146 - &lt;div class="sourceCode" id="cb11"&gt;&lt;pre 2147 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb11-1"&gt;&lt;a href="#cb11-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# alias it to `biscuits`&lt;/span&gt;&lt;/span&gt; 2148 - &lt;span id="cb11-2"&gt;&lt;a href="#cb11-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix registry add biscuits ~/mytemplates&lt;/span&gt; 2149 - &lt;span id="cb11-3"&gt;&lt;a href="#cb11-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2150 - &lt;span id="cb11-4"&gt;&lt;a href="#cb11-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# you will see it listed under `user` registries&lt;/span&gt;&lt;/span&gt; 2151 - &lt;span id="cb11-5"&gt;&lt;a href="#cb11-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix registry list&lt;/span&gt; 2152 - &lt;span id="cb11-6"&gt;&lt;a href="#cb11-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;.&lt;/span&gt; . .&lt;/span&gt; 2153 - &lt;span id="cb11-7"&gt;&lt;a href="#cb11-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;user&lt;/span&gt; flake:biscuits path:/home/np/template-tests&lt;/span&gt; 2154 - &lt;span id="cb11-8"&gt;&lt;a href="#cb11-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;.&lt;/span&gt; . .&lt;/span&gt; 2155 - &lt;span id="cb11-9"&gt;&lt;a href="#cb11-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2156 - &lt;span id="cb11-10"&gt;&lt;a href="#cb11-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;$&lt;/span&gt; nix flake init &lt;span class="at"&gt;-t&lt;/span&gt; biscuits#latex-report&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2157 - &lt;h2 id="extending-the-official-templates"&gt;Extending the official 2158 - templates&lt;/h2&gt; 2159 - &lt;p&gt;I personally, would like the &lt;code&gt;biscuits&lt;/code&gt; registry to 2160 - include not just my homemade templates, but also the templates from 2161 - &lt;code&gt;NixOS/templates&lt;/code&gt; (and maybe a couple of other repositories 2162 - in the wild):&lt;/p&gt; 2163 - &lt;div class="sourceCode" id="cb12"&gt;&lt;pre 2164 - class="sourceCode nix"&gt;&lt;code class="sourceCode nix"&gt;&lt;span id="cb12-1"&gt;&lt;a href="#cb12-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2165 - &lt;span id="cb12-2"&gt;&lt;a href="#cb12-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;description&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Pepper&amp;#39;s flake templates&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2166 - &lt;span id="cb12-3"&gt;&lt;a href="#cb12-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 2167 - &lt;span id="cb12-4"&gt;&lt;a href="#cb12-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;+ &lt;span class="va"&gt;inputs&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2168 - &lt;span id="cb12-5"&gt;&lt;a href="#cb12-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;+ &lt;span class="va"&gt;official-templates&lt;/span&gt;.&lt;span class="va"&gt;url&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="va"&gt;github&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt;&lt;span class="ss"&gt;NixOS/templates&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2169 - &lt;span id="cb12-6"&gt;&lt;a href="#cb12-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;+ &lt;span class="va"&gt;other-templates&lt;/span&gt;.&lt;span class="va"&gt;url&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="va"&gt;github&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt;&lt;span class="ss"&gt;some-other/templates&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2170 - &lt;span id="cb12-7"&gt;&lt;a href="#cb12-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;+ &lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 2171 - &lt;span id="cb12-8"&gt;&lt;a href="#cb12-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 2172 - &lt;span id="cb12-9"&gt;&lt;a href="#cb12-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;outputs&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="va"&gt;self&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="va"&gt;official-templates&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="va"&gt;other-templates&lt;/span&gt; &lt;span class="op"&gt;...&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;: &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2173 - &lt;span id="cb12-10"&gt;&lt;a href="#cb12-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 2174 - &lt;span id="cb12-11"&gt;&lt;a href="#cb12-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;templates&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2175 - &lt;span id="cb12-12"&gt;&lt;a href="#cb12-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;latex-report&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2176 - &lt;span id="cb12-13"&gt;&lt;a href="#cb12-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;path&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;./latex-report-template&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2177 - &lt;span id="cb12-14"&gt;&lt;a href="#cb12-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;description&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;A latex whitepaper project&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2178 - &lt;span id="cb12-15"&gt;&lt;a href="#cb12-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 2179 - &lt;span id="cb12-16"&gt;&lt;a href="#cb12-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;rust-hello&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2180 - &lt;span id="cb12-17"&gt;&lt;a href="#cb12-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;path&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="ss"&gt;./rust-hello-template&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2181 - &lt;span id="cb12-18"&gt;&lt;a href="#cb12-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;description&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Simple Hello World in Rust, with overloaded Rust toolchain&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2182 - &lt;span id="cb12-19"&gt;&lt;a href="#cb12-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 2183 - &lt;span id="cb12-20"&gt;&lt;a href="#cb12-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 2184 - &lt;span id="cb12-21"&gt;&lt;a href="#cb12-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="op"&gt;//&lt;/span&gt; official&lt;span class="op"&gt;-&lt;/span&gt;templates.templates&lt;/span&gt; 2185 - &lt;span id="cb12-22"&gt;&lt;a href="#cb12-22" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="op"&gt;//&lt;/span&gt; other&lt;span class="op"&gt;-&lt;/span&gt;templates.templates&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2186 - &lt;span id="cb12-23"&gt;&lt;a href="#cb12-23" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 2187 - &lt;span id="cb12-24"&gt;&lt;a href="#cb12-24" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 2188 - &lt;span id="cb12-25"&gt;&lt;a href="#cb12-25" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2189 - &lt;p&gt;Running &lt;code&gt;nix flake show biscuits&lt;/code&gt; will now list templates 2190 - from the &lt;code&gt;biscuits&lt;/code&gt; registry as well as the ones from 2191 - &lt;code&gt;NixOS/templates&lt;/code&gt;. Ensure that the names don’t collide 2192 - though.&lt;/p&gt;</description> 2193 - <link>https://oppi.li/posts/novice_nix:_flake_templates/</link> 2194 - <pubDate>Tue, 05 Oct 2021 16:49:00 +0000</pubDate> 2195 - <guid>https://oppi.li/posts/novice_nix:_flake_templates/</guid> 2196 - </item> 2197 - <item> 2198 - <title>SDL2 Devlog</title> 2199 - <description>&lt;p&gt;I have been working on an editor for the &lt;a 2200 - href="https://git.peppe.rs/graphics/obi/about"&gt;One Bit Image&lt;/a&gt; file 2201 - format in Rust and SDL2. This entry in my blog follows my progress on 2202 - the editor. The days are listed in reverse chronological order, begin 2203 - from the bottom, if this is your first time on this page.&lt;/p&gt; 2204 - &lt;h3 id="day-20"&gt;Day 20&lt;/h3&gt; 2205 - &lt;p&gt;More &lt;code&gt;lisp&lt;/code&gt; stuff! I added a new brush, for rectangular 2206 - selections. While selection doesn’t do much on its own, the selected 2207 - area can be passed onto a &lt;code&gt;lisp&lt;/code&gt; procedure, for example, a 2208 - procedure to draw horizontal black and white lines:&lt;/p&gt; 2209 - &lt;figure&gt; 2210 - &lt;video src="https://u.peppe.rs/frU.mp4" controls=""&gt;&lt;a 2211 - href="https://u.peppe.rs/frU.mp4"&gt;Day 20&lt;/a&gt;&lt;/video&gt; 2212 - &lt;figcaption aria-hidden="true"&gt;Day 20&lt;/figcaption&gt; 2213 - &lt;/figure&gt; 2214 - &lt;h3 id="day-19"&gt;Day 19&lt;/h3&gt; 2215 - &lt;p&gt;Attempted &lt;a href="https://peppe.rs/art/conduit.png"&gt;some isometric 2216 - art&lt;/a&gt; within the editor. The angles displayed alongside the line brush 2217 - are handly, however, having only a rectangular grid did not help. I 2218 - implemented an isometric grid today. Isometric grids in pixel art differ 2219 - in that the tangent of the isometric angle is exactly 0.5! For every 2220 - pixel down, you go exactly two pixels sideways. The math works out 2221 - really well in the drawing procedures too, dealing with floating points 2222 - is a pain.&lt;/p&gt; 2223 - &lt;figure&gt; 2224 - &lt;img src="https://u.peppe.rs/1Kb.png" alt="Day 19" /&gt; 2225 - &lt;figcaption aria-hidden="true"&gt;Day 19&lt;/figcaption&gt; 2226 - &lt;/figure&gt; 2227 - &lt;h3 id="day-18"&gt;Day 18&lt;/h3&gt; 2228 - &lt;p&gt;I added basic support for guides, they can be added and activated 2229 - from the &lt;code&gt;lisp&lt;/code&gt; REPL. Another long standing improvement I 2230 - wanted to make was reworking the pixmap drawing procedure. The old 2231 - procedure draws a square for each pixel in the pixmap, coloured 2232 - according to its value in the pixmap. Naturally, this means, for an 2233 - &lt;strong&gt;NxN&lt;/strong&gt; pixmap, there are &lt;strong&gt;N²&lt;/strong&gt; calls to SDL! 2234 - I reworked this procedure to compress each line of the pixmap using RLE 2235 - (run length encoding), and call out to SDL for each run in the line. 2236 - This drastically improved drawing speeds on larger grids. The following 2237 - is a comparison between the two procedures, the leftmost picture is the 2238 - rendered image, the middle picture is the optimized drawing procedure 2239 - (draws each run instead of pixel), and the right most picture is the 2240 - primitive drawing procedure (draws each pixel):&lt;/p&gt; 2241 - &lt;figure&gt; 2242 - &lt;img src="https://u.peppe.rs/U4B.png" alt="Day 18" /&gt; 2243 - &lt;figcaption aria-hidden="true"&gt;Day 18&lt;/figcaption&gt; 2244 - &lt;/figure&gt; 2245 - &lt;h3 id="day-17"&gt;Day 17&lt;/h3&gt; 2246 - &lt;p&gt;I decided to give the text-only statusline a touch up, by adding a 2247 - active color and dither level preview. Aligning the “widget” to the 2248 - right of statusline involved a lot more than I thought, so I created a 2249 - ghetto CSS-like rectangle placement system to position containers inside 2250 - containers:&lt;/p&gt; 2251 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre 2252 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// roughly something like this&lt;/span&gt;&lt;/span&gt; 2253 - &lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; statusline &lt;span class="op"&gt;=&lt;/span&gt; &lt;/span&gt; 2254 - &lt;span id="cb1-3"&gt;&lt;a href="#cb1-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;Container::&lt;/span&gt;new(&lt;span class="pp"&gt;Offset::&lt;/span&gt;Left(&lt;span class="dv"&gt;0&lt;/span&gt;)&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="pp"&gt;Offset::&lt;/span&gt;Bottom(&lt;span class="dv"&gt;40&lt;/span&gt;))&lt;/span&gt; 2255 - &lt;span id="cb1-4"&gt;&lt;a href="#cb1-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;.&lt;/span&gt;width(&lt;span class="pp"&gt;Size::&lt;/span&gt;Max)&lt;/span&gt; 2256 - &lt;span id="cb1-5"&gt;&lt;a href="#cb1-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;.&lt;/span&gt;height(&lt;span class="pp"&gt;Size::&lt;/span&gt;Absolute(&lt;span class="dv"&gt;20&lt;/span&gt;))&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2257 - &lt;span id="cb1-6"&gt;&lt;a href="#cb1-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 2258 - &lt;span id="cb1-7"&gt;&lt;a href="#cb1-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="kw"&gt;mut&lt;/span&gt; primary &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;Container::&lt;/span&gt;uninit()&lt;/span&gt; 2259 - &lt;span id="cb1-8"&gt;&lt;a href="#cb1-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;.&lt;/span&gt;width(&lt;span class="pp"&gt;Size::&lt;/span&gt;Absolute(&lt;span class="dv"&gt;16&lt;/span&gt;))&lt;/span&gt; 2260 - &lt;span id="cb1-9"&gt;&lt;a href="#cb1-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;.&lt;/span&gt;height(&lt;span class="pp"&gt;Size::&lt;/span&gt;Absolute(&lt;span class="dv"&gt;16&lt;/span&gt;))&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2261 - &lt;span id="cb1-10"&gt;&lt;a href="#cb1-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 2262 - &lt;span id="cb1-11"&gt;&lt;a href="#cb1-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;container&lt;span class="op"&gt;.&lt;/span&gt;place(&lt;/span&gt; 2263 - &lt;span id="cb1-12"&gt;&lt;a href="#cb1-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kw"&gt;mut&lt;/span&gt; padding_box&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 2264 - &lt;span id="cb1-13"&gt;&lt;a href="#cb1-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;HorAlign::&lt;/span&gt;Right&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 2265 - &lt;span id="cb1-14"&gt;&lt;a href="#cb1-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;VertAlign::&lt;/span&gt;Center&lt;/span&gt; 2266 - &lt;span id="cb1-15"&gt;&lt;a href="#cb1-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2267 - &lt;p&gt;The result (brush preview on the bottom right):&lt;/p&gt; 2268 - &lt;figure&gt; 2269 - &lt;video src="https://u.peppe.rs/OtU.mp4" controls=""&gt;&lt;a 2270 - href="https://u.peppe.rs/OtU.mp4"&gt;Day 17&lt;/a&gt;&lt;/video&gt; 2271 - &lt;figcaption aria-hidden="true"&gt;Day 17&lt;/figcaption&gt; 2272 - &lt;/figure&gt; 2273 - &lt;h3 id="day-16"&gt;Day 16&lt;/h3&gt; 2274 - &lt;p&gt;The embedded lisp is coming along nicely, users can load a custom 2275 - &lt;code&gt;rc.lisp&lt;/code&gt;, which is evaluated on startup. To disable to grid 2276 - on start, for example:&lt;/p&gt; 2277 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 2278 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;;;; rc.lisp&lt;/span&gt;&lt;/span&gt; 2279 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;(toggle-grid)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2280 - &lt;p&gt;Some aliases to switch between brushes:&lt;/p&gt; 2281 - &lt;div class="sourceCode" id="cb3"&gt;&lt;pre 2282 - class="sourceCode scheme"&gt;&lt;code class="sourceCode scheme"&gt;&lt;span id="cb3-1"&gt;&lt;a href="#cb3-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;;;; rc.lisp&lt;/span&gt;&lt;/span&gt; 2283 - &lt;span id="cb3-2"&gt;&lt;a href="#cb3-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;(&lt;span class="ex"&gt;define&lt;/span&gt;&lt;span class="fu"&gt; &lt;/span&gt;(brush kind)&lt;/span&gt; 2284 - &lt;span id="cb3-3"&gt;&lt;a href="#cb3-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (&lt;span class="kw"&gt;cond&lt;/span&gt;&lt;/span&gt; 2285 - &lt;span id="cb3-4"&gt;&lt;a href="#cb3-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; ((&lt;span class="kw"&gt;eq?&lt;/span&gt; kind &amp;#39;f) (brush-fill))&lt;/span&gt; 2286 - &lt;span id="cb3-5"&gt;&lt;a href="#cb3-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; ((&lt;span class="kw"&gt;eq?&lt;/span&gt; kind &amp;#39;c) (brush-circle))&lt;/span&gt; 2287 - &lt;span id="cb3-6"&gt;&lt;a href="#cb3-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; ((&lt;span class="kw"&gt;eq?&lt;/span&gt; kind &amp;#39;l) (brush-line))&lt;/span&gt; 2288 - &lt;span id="cb3-7"&gt;&lt;a href="#cb3-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; ((&lt;span class="kw"&gt;eq?&lt;/span&gt; kind &amp;#39;l+) (brush-line-extend))&lt;/span&gt; 2289 - &lt;span id="cb3-8"&gt;&lt;a href="#cb3-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (&lt;span class="kw"&gt;else&lt;/span&gt; (brush-circle))))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2290 - &lt;p&gt;The following script draws a straight line along a given axis, at a 2291 - given distance from the canvas boundary:&lt;/p&gt; 2292 - &lt;figure&gt; 2293 - &lt;video src="https://u.peppe.rs/b3i.mp4" controls=""&gt;&lt;a 2294 - href="https://u.peppe.rs/b3i.mp4"&gt;Day 16&lt;/a&gt;&lt;/video&gt; 2295 - &lt;figcaption aria-hidden="true"&gt;Day 16&lt;/figcaption&gt; 2296 - &lt;/figure&gt; 2297 - &lt;h3 id="day-15"&gt;Day 15&lt;/h3&gt; 2298 - &lt;p&gt;I began writing a standard library for the lisp, in lisp. It includes 2299 - basic list operations: &lt;code&gt;car&lt;/code&gt;, &lt;code&gt;cdr&lt;/code&gt;, 2300 - &lt;code&gt;null?&lt;/code&gt;, &lt;code&gt;list&lt;/code&gt;, higher order functions: 2301 - &lt;code&gt;map&lt;/code&gt;, &lt;code&gt;filter&lt;/code&gt;, &lt;code&gt;fold&lt;/code&gt;:&lt;/p&gt; 2302 - &lt;div class="sourceCode" id="cb4"&gt;&lt;pre 2303 - class="sourceCode lisp"&gt;&lt;code class="sourceCode commonlisp"&gt;&lt;span id="cb4-1"&gt;&lt;a href="#cb4-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;(define (member? item ls)&lt;/span&gt; 2304 - &lt;span id="cb4-2"&gt;&lt;a href="#cb4-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (fold &lt;span class="dv"&gt;#f&lt;/span&gt;&lt;/span&gt; 2305 - &lt;span id="cb4-3"&gt;&lt;a href="#cb4-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; (&lt;span class="kw"&gt;lambda&lt;/span&gt; (acc x) (&lt;span class="kw"&gt;or&lt;/span&gt; acc (eq? item x)))&lt;/span&gt; 2306 - &lt;span id="cb4-4"&gt;&lt;a href="#cb4-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; ls))&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2307 - &lt;h3 id="day-14"&gt;Day 14&lt;/h3&gt; 2308 - &lt;p&gt;I attempted a &lt;a href="https://peppe.rs/art/ramen_noodles.png"&gt;small 2309 - art piece&lt;/a&gt; using the editor, while it was largely usable, I felt a 2310 - certain lack of feedback. The brushes just didn’t relay as much info as 2311 - I’d have liked, for example, the approximate points of the line or the 2312 - angle made by the line against the x-axis. Unfortunately, the existing 2313 - infrastructure around brushes and line drawing didn’t easily allow for 2314 - this either. I went ahead and reimplemented brushes, and added a new 2315 - flood fill brush too:&lt;/p&gt; 2316 - &lt;figure&gt; 2317 - &lt;video src="https://u.peppe.rs/8q.mp4" controls=""&gt;&lt;a 2318 - href="https://u.peppe.rs/8q.mp4"&gt;Day 14&lt;/a&gt;&lt;/video&gt; 2319 - &lt;figcaption aria-hidden="true"&gt;Day 14&lt;/figcaption&gt; 2320 - &lt;/figure&gt; 2321 - &lt;h3 id="day-13"&gt;Day 13&lt;/h3&gt; 2322 - &lt;p&gt;I added a few more forms to the &lt;code&gt;lisp&lt;/code&gt; evaluator. It 2323 - handles recursion, definitions, variable mutation and more. The prelude 2324 - contains 20 subroutines so far, including comparision and logic 2325 - operators. The REPL interface on the SDL side requires some UX tweaks; 2326 - environment based completion, readline motions sound doable.&lt;/p&gt; 2327 - &lt;figure&gt; 2328 - &lt;video src="https://u.peppe.rs/u3.mp4" controls=""&gt;&lt;a 2329 - href="https://u.peppe.rs/u3.mp4"&gt;Day 13&lt;/a&gt;&lt;/video&gt; 2330 - &lt;figcaption aria-hidden="true"&gt;Day 13&lt;/figcaption&gt; 2331 - &lt;/figure&gt; 2332 - &lt;h3 id="day-12"&gt;Day 12&lt;/h3&gt; 2333 - &lt;p&gt;I lifted most of &lt;a 2334 - href="https://github.com/murarth/ketos"&gt;murarth/ketos&lt;/a&gt; into the 2335 - editor. &lt;code&gt;ketos&lt;/code&gt;’s implementation of &lt;code&gt;lisp&lt;/code&gt; is too 2336 - vast for my use case. For example, the editor does not need data types 2337 - to handle raw strings or byte strings. I have got a basic evaluator 2338 - running inside the SDL2 context (notice the &lt;code&gt;lisp&lt;/code&gt; REPL at 2339 - the bottom of the window). Over the following days, I intend to create a 2340 - set of prelude functions to manipulate the pixmap. Users can implement 2341 - their own brushes, dithering patterns, keybinds and more 2342 - (hopefully).&lt;/p&gt; 2343 - &lt;figure&gt; 2344 - &lt;video src="https://u.peppe.rs/y0.mp4" controls=""&gt;&lt;a 2345 - href="https://u.peppe.rs/y0.mp4"&gt;Day 12&lt;/a&gt;&lt;/video&gt; 2346 - &lt;figcaption aria-hidden="true"&gt;Day 12&lt;/figcaption&gt; 2347 - &lt;/figure&gt; 2348 - &lt;h3 id="day-11"&gt;Day 11&lt;/h3&gt; 2349 - &lt;p&gt;I intend to supplement the editor with scripting language and an 2350 - inbuilt REPL for the same. I began by implementing a text box widget 2351 - from scratch, with history and readline like editing:&lt;/p&gt; 2352 - &lt;figure&gt; 2353 - &lt;video src="https://u.peppe.rs/Mh.mp4" controls=""&gt;&lt;a 2354 - href="https://u.peppe.rs/Mh.mp4"&gt;Day 11&lt;/a&gt;&lt;/video&gt; 2355 - &lt;figcaption aria-hidden="true"&gt;Day 11&lt;/figcaption&gt; 2356 - &lt;/figure&gt; 2357 - &lt;h3 id="day-10"&gt;Day 10&lt;/h3&gt; 2358 - &lt;p&gt;I started reading up on dithering methods and half-toning, I wanted 2359 - to create a dithering brush that would automatically produce popular 2360 - dithering patterns. The method that caught my eye (and also the one used 2361 - most often in pixel art), was Bayer’s ordered dithering. When applied to 2362 - a black and white image, each pixel, based on its intensity, is mapped 2363 - to a 4x4 grid of pixels. A completely empty (completely black) 4x4 grid 2364 - represents zero intensity, and a filled 4x4 grid represents full 2365 - intensity. Bayer’s ordered dithering can produce 15 steps of intensity 2366 - between zero and full (by switching on exactly 1 pixel more at each 2367 - level), thus, being able to draw 17 “shades” from white to black. 2368 - Creating a dithering brush from here was fairly trivial. Our pixmap is 2369 - supposed to represent the final dithered image, it must be divided into 2370 - 4x4 grids. Each grid is colored based on the intensity of the brush 2371 - passing over it:&lt;/p&gt; 2372 - &lt;figure&gt; 2373 - &lt;img src="https://u.peppe.rs/Mn.png" alt="Day 10" /&gt; 2374 - &lt;figcaption aria-hidden="true"&gt;Day 10&lt;/figcaption&gt; 2375 - &lt;/figure&gt; 2376 - &lt;h3 id="day-9"&gt;Day 9&lt;/h3&gt; 2377 - &lt;p&gt;I started working towards an interface. I like the idea of a largely 2378 - read-only HUD, i. e., an interface that simply describes the state of 2379 - the application. Changes to this state are initiated via keybinds or 2380 - text commands. I am proud of the symmetry indicator; &lt;code&gt;-&lt;/code&gt; for 2381 - horizontal symmetry, &lt;code&gt;|&lt;/code&gt; for vertical symmetry, 2382 - &lt;code&gt;+&lt;/code&gt; for radial symmetry.&lt;/p&gt; 2383 - &lt;figure&gt; 2384 - &lt;img src="https://u.peppe.rs/hx.png" alt="Day 9" /&gt; 2385 - &lt;figcaption aria-hidden="true"&gt;Day 9&lt;/figcaption&gt; 2386 - &lt;/figure&gt; 2387 - &lt;h3 id="day-8"&gt;Day 8&lt;/h3&gt; 2388 - &lt;p&gt;One of my favourite features of GIMP was symmetric editing. I added 2389 - some coordinate geometry primitives to my pixmap abstraction, allowing 2390 - for mirroring and reflecting figures about lines or points. The result 2391 - was an ergonomic function that applies symmetry to any painting 2392 - operation, (undo/redo works as expected):&lt;/p&gt; 2393 - &lt;div class="sourceCode" id="cb5"&gt;&lt;pre 2394 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb5-1"&gt;&lt;a href="#cb5-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; line &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;self&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;pixmap&lt;span class="op"&gt;.&lt;/span&gt;get_line(start&lt;span class="op"&gt;,&lt;/span&gt; end)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2395 - &lt;span id="cb5-2"&gt;&lt;a href="#cb5-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; sym_line &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;self&lt;/span&gt;&lt;span class="op"&gt;.&lt;/span&gt;symmetry&lt;span class="op"&gt;.&lt;/span&gt;apply(&lt;span class="op"&gt;&amp;amp;&lt;/span&gt;line)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2396 - &lt;span id="cb5-3"&gt;&lt;a href="#cb5-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;for&lt;/span&gt; point on line&lt;span class="op"&gt;.&lt;/span&gt;extend(sym_line) &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2397 - &lt;span id="cb5-4"&gt;&lt;a href="#cb5-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="co"&gt;// draw to window&lt;/span&gt;&lt;/span&gt; 2398 - &lt;span id="cb5-5"&gt;&lt;a href="#cb5-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2399 - &lt;figure&gt; 2400 - &lt;video src="https://u.peppe.rs/B1.mp4" controls=""&gt;&lt;a 2401 - href="https://u.peppe.rs/B1.mp4"&gt;Day 8&lt;/a&gt;&lt;/video&gt; 2402 - &lt;figcaption aria-hidden="true"&gt;Day 8&lt;/figcaption&gt; 2403 - &lt;/figure&gt; 2404 - &lt;h3 id="day-7"&gt;Day 7&lt;/h3&gt; 2405 - &lt;p&gt;Bresenham saves the day again! This time, I implemented his line 2406 - drawing algorithm, to, well, draw lines. Each point on the line is then 2407 - “buffed” based on the active brush size. Today’s changes fit in very 2408 - well with the undo system and the brush size feature. Creating the right 2409 - abstractions, one at a time :)&lt;/p&gt; 2410 - &lt;figure&gt; 2411 - &lt;video src="https://u.peppe.rs/xt.mp4" controls=""&gt;&lt;a 2412 - href="https://u.peppe.rs/xt.mp4"&gt;Day 7&lt;/a&gt;&lt;/video&gt; 2413 - &lt;figcaption aria-hidden="true"&gt;Day 7&lt;/figcaption&gt; 2414 - &lt;/figure&gt; 2415 - &lt;h3 id="day-6"&gt;Day 6&lt;/h3&gt; 2416 - &lt;p&gt;I extended Bresenham’s algorithm to draw not just circle outlines, 2417 - but also generate their fills. Unlike Bresenham’s algorithm, this 2418 - variant generates points for two quadrants at once, these points are 2419 - mirrored over the dividing axis to generate the other two quadrants.&lt;/p&gt; 2420 - &lt;figure&gt; 2421 - &lt;img src="https://u.peppe.rs/f3.png" alt="Day 6" /&gt; 2422 - &lt;figcaption aria-hidden="true"&gt;Day 6&lt;/figcaption&gt; 2423 - &lt;/figure&gt; 2424 - &lt;h3 id="day-5"&gt;Day 5&lt;/h3&gt; 2425 - &lt;p&gt;I discovered and implemented Bresenham’s algorithm for efficient 2426 - circle drawing. The algorithm allowed for sized circular brushes, 2427 - something I really liked from GIMP. Very convenient that the Wikipedia 2428 - page for Bresenham’s algorithm also includes a section about optimizing 2429 - for integer based arithmetic. I managed to abstract out another giant 2430 - component of the application, the pixmap. Any image is just a grid of 2431 - pixels (a pixmap), where the pixel’s value is decided by the application 2432 - (1-bit in my case). I could potentially extend the application to a 2433 - 24-bit image editor!&lt;/p&gt; 2434 - &lt;figure&gt; 2435 - &lt;video src="https://u.peppe.rs/Kh.mp4" controls=""&gt;&lt;a 2436 - href="https://u.peppe.rs/Kh.mp4"&gt;Day 5&lt;/a&gt;&lt;/video&gt; 2437 - &lt;figcaption aria-hidden="true"&gt;Day 5&lt;/figcaption&gt; 2438 - &lt;/figure&gt; 2439 - &lt;h3 id="day-4"&gt;Day 4&lt;/h3&gt; 2440 - &lt;p&gt;I created a generic “undo stack” data structure that allows for 2441 - infinite “undos” and “redos”. Every modification operation to the grid 2442 - is persisted to the application state. A couple of keybinds allow the 2443 - user to revert and re-apply these operations! I expect abstracting this 2444 - component will come in handy down the line.&lt;/p&gt; 2445 - &lt;figure&gt; 2446 - &lt;video src="https://u.peppe.rs/w5.mp4" controls=""&gt;&lt;a 2447 - href="https://u.peppe.rs/w5.mp4"&gt;Day 4&lt;/a&gt;&lt;/video&gt; 2448 - &lt;figcaption aria-hidden="true"&gt;Day 4&lt;/figcaption&gt; 2449 - &lt;/figure&gt; 2450 - &lt;h3 id="day-3"&gt;Day 3&lt;/h3&gt; 2451 - &lt;p&gt;I implemented the bare minimum required to call the program an 2452 - “editor”. The application displays a grid, tracks mouse events, paints 2453 - white to the canvas on left click, and black to the canvas on right 2454 - click. I created a make-shift MVC architecture à la Elm in Rust.&lt;/p&gt; 2455 - &lt;figure&gt; 2456 - &lt;video src="https://u.peppe.rs/GF.mp4" controls=""&gt;&lt;a 2457 - href="https://u.peppe.rs/GF.mp4"&gt;Day 3&lt;/a&gt;&lt;/video&gt; 2458 - &lt;figcaption aria-hidden="true"&gt;Day 3&lt;/figcaption&gt; 2459 - &lt;/figure&gt; 2460 - &lt;h3 id="day-2"&gt;Day 2&lt;/h3&gt; 2461 - &lt;p&gt;I started figuring out event handling today. Implemented a couple of 2462 - keybinds to zoom in/out of the drawing area. Conversions of SDL2 2463 - coordinates (measured in signed 32 bit integers) to my internal “drawing 2464 - area” coordinates (measured in unsigned 32 bit integers) is very 2465 - annoying. Hopefully the unchecked conversions won’t haunt me later.&lt;/p&gt; 2466 - &lt;figure&gt; 2467 - &lt;video src="https://u.peppe.rs/L4.mp4" controls=""&gt;&lt;a 2468 - href="https://u.peppe.rs/L4.mp4"&gt;Day 2&lt;/a&gt;&lt;/video&gt; 2469 - &lt;figcaption aria-hidden="true"&gt;Day 2&lt;/figcaption&gt; 2470 - &lt;/figure&gt; 2471 - &lt;h3 id="day-1"&gt;Day 1&lt;/h3&gt; 2472 - &lt;p&gt;Getting started with Rust and SDL2 is very straightforward. The 2473 - &lt;code&gt;rust-sdl2&lt;/code&gt; library contains some detailed examples that 2474 - allowed me to get all the way to drawing a grid from a 2475 - &lt;code&gt;Vec&amp;lt;bool&amp;gt;&lt;/code&gt;:&lt;/p&gt; 2476 - &lt;figure&gt; 2477 - &lt;img src="https://u.peppe.rs/Ma.png" alt="Day 1" /&gt; 2478 - &lt;figcaption aria-hidden="true"&gt;Day 1&lt;/figcaption&gt; 2479 - &lt;/figure&gt;</description> 2480 - <link>https://oppi.li/posts/SDL2_devlog/</link> 2481 - <pubDate>Sun, 11 Apr 2021 10:46:00 +0000</pubDate> 2482 - <guid>https://oppi.li/posts/SDL2_devlog/</guid> 2483 - </item> 2484 - <item> 2485 - <title>Self-hosting Git</title> 2486 - <description>&lt;p&gt;Earlier this week, I began migrating my repositories from Github to 2487 - &lt;a href="https://git.zx2c4.com/cgit/about/"&gt;cgit&lt;/a&gt;. If you care at all 2488 - about big corporates turning open-source into a T-shirt farming service, 2489 - this is the way to go.&lt;/p&gt; 2490 - &lt;h3 id="offerings"&gt;Offerings&lt;/h3&gt; 2491 - &lt;p&gt;cgit is &lt;em&gt;very&lt;/em&gt; bare bones. It is &lt;a 2492 - href="https://tools.ietf.org/html/rfc3875"&gt;cgi-based&lt;/a&gt; web interface 2493 - to git, and nothing more. You may browse repositories, view diffs, 2494 - commit logs and even clone via http. If you are looking to replace 2495 - Github with cgit, keep in mind that cgit does not handle issues or 2496 - pull/merge requests. If people wish to contribute to your work, they 2497 - would have to send you a patch via email.&lt;/p&gt; 2498 - &lt;h3 id="setup"&gt;Setup&lt;/h3&gt; 2499 - &lt;p&gt;Installing cgit is fairly straightforward, if you would like to 2500 - compile it from source:&lt;/p&gt; 2501 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre class="sourceCode sh"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# fetch&lt;/span&gt;&lt;/span&gt; 2502 - &lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;git&lt;/span&gt; clone https://git.zx2c4.com &lt;span class="kw"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="bu"&gt;cd&lt;/span&gt; cgit&lt;/span&gt; 2503 - &lt;span id="cb1-3"&gt;&lt;a href="#cb1-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;git&lt;/span&gt; submodule init&lt;/span&gt; 2504 - &lt;span id="cb1-4"&gt;&lt;a href="#cb1-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;git&lt;/span&gt; submodule update&lt;/span&gt; 2505 - &lt;span id="cb1-5"&gt;&lt;a href="#cb1-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2506 - &lt;span id="cb1-6"&gt;&lt;a href="#cb1-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# install&lt;/span&gt;&lt;/span&gt; 2507 - &lt;span id="cb1-7"&gt;&lt;a href="#cb1-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;make&lt;/span&gt; NO_LUA=1&lt;/span&gt; 2508 - &lt;span id="cb1-8"&gt;&lt;a href="#cb1-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;sudo&lt;/span&gt; make install&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2509 - &lt;p&gt;This would drop the cgit cgi script (and the default css) into 2510 - &lt;code&gt;/var/www/htdocs/cgit&lt;/code&gt;. You may configure cgit by editing 2511 - &lt;code&gt;/etc/cgitrc&lt;/code&gt;. I specify the &lt;code&gt;NO_LUA&lt;/code&gt; flag to 2512 - compile without lua support, exclude that flag if you would like to 2513 - extend cgit via lua scripts.&lt;/p&gt; 2514 - &lt;h3 id="going-live"&gt;Going live&lt;/h3&gt; 2515 - &lt;p&gt;You might want to use, &lt;a 2516 - href="https://github.com/gnosek/fcgiwrap"&gt;fcgiwrap&lt;/a&gt;, a &lt;a 2517 - href="http://www.nongnu.org/fastcgi"&gt;fastcgi&lt;/a&gt; wrapper for 2518 - &lt;code&gt;cgi&lt;/code&gt; scripts,&lt;/p&gt; 2519 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre class="sourceCode sh"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;sudo&lt;/span&gt; apt install fcgiwrap&lt;/span&gt; 2520 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;sudo&lt;/span&gt; systemctl start fcgiwrap.socket&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2521 - &lt;p&gt;Expose the cgit cgi script to the web via &lt;code&gt;nginx&lt;/code&gt;:&lt;/p&gt; 2522 - &lt;pre&gt;&lt;code&gt;# nginx.conf 2523 - server { 2524 - listen 80; 2525 - server_name git.example.com; 2526 - 2527 - # serve static files 2528 - location ~* ^.+\.(css|png|ico)$ { 2529 - root /var/www/htdocs/cgit; 2530 - } 2531 - 2532 - location / { 2533 - fastcgi_pass unix:/run/fcgiwrap.socket; 2534 - fastcgi_param SCRIPT_FILENAME /var/www/htdocs/cgit/cgit.cgi; # the default location of the cgit cgi script 2535 - fastcgi_param PATH_INFO $uri; 2536 - fastcgi_param QUERY_STRING $args; 2537 - } 2538 - }&lt;/code&gt;&lt;/pre&gt; 2539 - &lt;p&gt;Point cgit to your git repositories:&lt;/p&gt; 2540 - &lt;pre&gt;&lt;code&gt;# /etc/cgitrc 2541 - scan-path=/path/to/git/repos&lt;/code&gt;&lt;/pre&gt; 2542 - &lt;p&gt;&lt;strong&gt;&lt;em&gt;Note&lt;/em&gt;&lt;/strong&gt;: &lt;em&gt;&lt;code&gt;scan-path&lt;/code&gt; works best 2543 - if you stick it at the end of your &lt;code&gt;cgitrc&lt;/code&gt;&lt;/em&gt;.&lt;/p&gt; 2544 - &lt;p&gt;You may now create remote repositories at 2545 - &lt;code&gt;/path/to/git/repos&lt;/code&gt;, via:&lt;/p&gt; 2546 - &lt;pre&gt;&lt;code&gt;git init --bare&lt;/code&gt;&lt;/pre&gt; 2547 - &lt;p&gt;Add the remote to your local repository:&lt;/p&gt; 2548 - &lt;pre&gt;&lt;code&gt;git remote set-url origin user@remote:/above/path 2549 - git push origin master&lt;/code&gt;&lt;/pre&gt; 2550 - &lt;h3 id="configuration"&gt;Configuration&lt;/h3&gt; 2551 - &lt;p&gt;cgit is fairly easy to configure, all configuration options can be 2552 - found &lt;a href="https://git.zx2c4.com/cgit/tree/cgitrc.5.txt"&gt;in the 2553 - manual&lt;/a&gt;, here are a couple of cool ones though:&lt;/p&gt; 2554 - &lt;p&gt;&lt;strong&gt;enable-commit-graph&lt;/strong&gt;: Generates a text based 2555 - graphical representation of the commit history, similar to 2556 - &lt;code&gt;git log --graph --oneline&lt;/code&gt;.&lt;/p&gt; 2557 - &lt;pre&gt;&lt;code&gt;| * | Add support for configuration file 2558 - * | | simplify command parsing logic 2559 - * | | Refactor parsers 2560 - * | | Add basic tests 2561 - * | | Merge remote-tracking branch &amp;#39;origin/master&amp;#39; in... 2562 - |\| | 2563 - | * | add installation instructions for nix 2564 - | * | switch to pancurses backendv0.2.2 2565 - | * | bump to v0.2.2 2566 - * | | Merge branch &amp;#39;master&amp;#39; into feature/larger-names... 2567 - |\| | 2568 - | * | enable feature based compilation to support win... 2569 - | * | remove dependency on rustc v1.45, bump to v0.2.... 2570 - | * | Merge branch &amp;#39;feature/windows&amp;#39; of https://git... 2571 - | |\ \ 2572 - | | * | add windows to github actions 2573 - | | * | switch to crossterm backend 2574 - | | * | Merge branch &amp;#39;fix/duplicate-habits&amp;#39; 2575 - | | |\ \ 2576 - | | | * | move duplicate check to command parsing blo...&lt;/code&gt;&lt;/pre&gt; 2577 - &lt;p&gt;&lt;strong&gt;section-from-path&lt;/strong&gt;: This option paired with 2578 - &lt;code&gt;scan-path&lt;/code&gt; will automatically generate sections in your cgit 2579 - index page, from the path to each repo. For example, the directory 2580 - structure used to generate sections on &lt;a href="https://git.peppe.rs"&gt;my 2581 - cgit instance&lt;/a&gt; looks like this:&lt;/p&gt; 2582 - &lt;pre&gt;&lt;code&gt;├── cli 2583 - │ ├── dijo 2584 - │ ├── eva 2585 - │ ├── pista 2586 - │ ├── taizen 2587 - │ └── xcursorlocate 2588 - ├── config 2589 - │ ├── dotfiles 2590 - │ └── nixos 2591 - ├── fonts 2592 - │ ├── curie 2593 - │ └── scientifica 2594 - ├── languages 2595 - │ └── lisk 2596 - ├── libs 2597 - │ ├── cutlass 2598 - │ └── fondant 2599 - ├── terminfo 2600 - ├── university 2601 - │ └── furby 2602 - └── web 2603 - └── isostatic&lt;/code&gt;&lt;/pre&gt; 2604 - &lt;h3 id="ease-of-use"&gt;Ease of use&lt;/h3&gt; 2605 - &lt;p&gt;As I mentioned before, &lt;code&gt;cgit&lt;/code&gt; is simply a view into your 2606 - git repositories, you will have to manually create new repositories by 2607 - entering your remote and using &lt;code&gt;git init --bare&lt;/code&gt;. Here are a 2608 - couple of scripts I wrote to perform actions on remotes, think of it as 2609 - a smaller version of Github’s &lt;code&gt;gh&lt;/code&gt; program.&lt;/p&gt; 2610 - &lt;p&gt;You may save these scripts as &lt;code&gt;git-script-name&lt;/code&gt; and drop 2611 - them in your &lt;code&gt;$PATH&lt;/code&gt;, and git will automatically add an alias 2612 - called &lt;code&gt;script-name&lt;/code&gt;, callable via:&lt;/p&gt; 2613 - &lt;pre&gt;&lt;code&gt;git script-name&lt;/code&gt;&lt;/pre&gt; 2614 - &lt;h4 id="git-new-repo"&gt;git-new-repo&lt;/h4&gt; 2615 - &lt;p&gt;Creates a new repository on your remote, the first arg may be a path 2616 - (section/repo-name) or just the repo name:&lt;/p&gt; 2617 - &lt;pre&gt;&lt;code&gt;#! /usr/bin/env bash 2618 - # 2619 - # usage: 2620 - # git new-repo section/repo-name 2621 - # 2622 - # example: 2623 - # git new-repo fonts/scientifica 2624 - # creates: user@remote:fonts/scientifica 2625 - 2626 - if [ $# -eq 0 ]; then 2627 - echo &amp;quot;requires an arg&amp;quot; 2628 - exit 1 2629 - fi 2630 - 2631 - ssh user@remote git init --bare &amp;quot;$1&amp;quot;;&lt;/code&gt;&lt;/pre&gt; 2632 - &lt;h4 id="git-set-desc"&gt;git-set-desc&lt;/h4&gt; 2633 - &lt;p&gt;To set a one line repository description. It simply copies the local 2634 - &lt;code&gt;.git/description&lt;/code&gt;, into &lt;code&gt;remote/description&lt;/code&gt;. 2635 - &lt;code&gt;cgit&lt;/code&gt; displays the contents of this file on the index 2636 - page:&lt;/p&gt; 2637 - &lt;pre&gt;&lt;code&gt;#! /usr/bin/env bash 2638 - # 2639 - # usage: 2640 - # enter repo description into .git/description and run: 2641 - # git set-desc 2642 - 2643 - remote=$(git remote get-url --push origin) 2644 - scp .git/description &amp;quot;$remote/description&amp;quot;&lt;/code&gt;&lt;/pre&gt;</description> 2645 - <link>https://oppi.li/posts/self-hosting_git/</link> 2646 - <pubDate>Sat, 17 Oct 2020 07:04:00 +0000</pubDate> 2647 - <guid>https://oppi.li/posts/self-hosting_git/</guid> 2648 - </item> 2649 - <item> 2650 - <title>NixOS</title> 2651 - <description>&lt;p&gt;I have been eyeing operating systems with functional package managers 2652 - for a while now, aka, NixOS or Guix. Reproducible builds, declarative 2653 - and rollback-able system configuration, system consistency, all sound 2654 - pretty cool. I have been using NixOS for about a month now.&lt;/p&gt; 2655 - &lt;h3 id="installation"&gt;Installation&lt;/h3&gt; 2656 - &lt;p&gt;I went with their minimal installation ISO. The installation was 2657 - pretty smooth from start to end, no hitches there. The entire &lt;a 2658 - href="https://nixos.org/manual/nixos/stable/"&gt;manual&lt;/a&gt; is available 2659 - offline, and is accessible during the installation. Very handy.&lt;/p&gt; 2660 - &lt;h3 id="setup"&gt;Setup&lt;/h3&gt; 2661 - &lt;p&gt;The entire system is configured via 2662 - &lt;code&gt;/etc/nixos/configuration.nix&lt;/code&gt;. Wifi, &lt;code&gt;libinput&lt;/code&gt; 2663 - gestures, audio, locale settings, there are options for literally 2664 - everything. You can declaratively write down the packages you want 2665 - installed too. With fresh installs of most distros, I usually fumble 2666 - with getting things like screen backlight and media keys to work. If I 2667 - do manage to fix it, I can’t carry it forward to future installations 2668 - trivially. Getting all my hardware to work on NixOS is as easy as:&lt;/p&gt; 2669 - &lt;pre&gt;&lt;code&gt;{ 2670 - server.xserver.libinput.enable = true; # touchpad 2671 - programs.light.enable = true; # backlight 2672 - hardware.pulseaudio.enable = true; # audio 2673 - networking.wireless.enable = true; # wifi 2674 - }&lt;/code&gt;&lt;/pre&gt; 2675 - &lt;h3 id="developing-with-nix"&gt;Developing with Nix&lt;/h3&gt; 2676 - &lt;p&gt;Nix makes it easy to enter environments that aren’t affected by your 2677 - system configuration using &lt;code&gt;nix-shell&lt;/code&gt;.&lt;/p&gt; 2678 - &lt;p&gt;Builds may be generated by specifying a &lt;code&gt;default.nix&lt;/code&gt; 2679 - file, and running &lt;code&gt;nix-build&lt;/code&gt;. Conventional package managers 2680 - require you to specify a dependency list, but there is no guarantee that 2681 - this list is complete. The package will build on your machine even if 2682 - you forget a dependency. However, with Nix, packages are installed to 2683 - &lt;code&gt;/nix/store&lt;/code&gt;, and not global paths such as 2684 - &lt;code&gt;/usr/bin/...&lt;/code&gt;, if your project builds, it means you have 2685 - included every last one.&lt;/p&gt; 2686 - &lt;p&gt;Issues on most my projects have been “unable to build because 2687 - &lt;code&gt;libxcb&lt;/code&gt; is missing”, or “this version of 2688 - &lt;code&gt;openssl&lt;/code&gt; is too old”. Tools like &lt;code&gt;cargo&lt;/code&gt; and 2689 - &lt;code&gt;pip&lt;/code&gt; are poor package managers. While they &lt;em&gt;can&lt;/em&gt; 2690 - guarantee that Rust or Python dependencies are met, they make 2691 - assumptions about the target system.&lt;/p&gt; 2692 - &lt;p&gt;For example, &lt;a href="https://github.com/nerdypepper/site"&gt;this 2693 - website&lt;/a&gt; is now built using Nix, anyone using Nix may simply, clone 2694 - the repository and run &lt;code&gt;./generate.sh&lt;/code&gt;, and it would &lt;em&gt;just 2695 - work&lt;/em&gt;, while keeping your global namespace clean™:&lt;/p&gt; 2696 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 2697 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;#! /usr/bin/env nix-shell&lt;/span&gt;&lt;/span&gt; 2698 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;#! nix-shell -i bash -p eva pandoc esh&lt;/span&gt;&lt;/span&gt; 2699 - &lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2700 - &lt;span id="cb2-4"&gt;&lt;a href="#cb2-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# some bash magic ;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2701 - &lt;p&gt;Dependencies are included with the &lt;code&gt;-p&lt;/code&gt; flag, the shell 2702 - script is executed with an interpreter, specified with the 2703 - &lt;code&gt;-i&lt;/code&gt; flag.&lt;/p&gt; 2704 - &lt;h3 id="impressions"&gt;Impressions&lt;/h3&gt; 2705 - &lt;p&gt;NixOS is by no means, simple. As a newcomer, using Nix was not easy, 2706 - heck, I had to learn a purely functional, lazy language to just build 2707 - programs. There is a lot to be desired on the tooling front as well. A 2708 - well fleshed out LSP plugin would be nice (&lt;a 2709 - href="https://github.com/nix-community/rnix-lsp"&gt;rnix-lsp looks 2710 - promising&lt;/a&gt;).&lt;/p&gt; 2711 - &lt;p&gt;Being able to rollback changes at a system level is cool. Package 2712 - broke something? Just &lt;code&gt;nixos-rebuild switch --rollback&lt;/code&gt;! 2713 - Deleted &lt;code&gt;nix&lt;/code&gt; by mistake? Find the binary in 2714 - &lt;code&gt;/nix/store&lt;/code&gt; and rollback! You aren’t punished for not 2715 - thinking twice.&lt;/p&gt; 2716 - &lt;p&gt;I don’t see myself switching to anything else in the near future, 2717 - NixOS does a lot of things right. If I ever need to reinstall NixOS, I 2718 - can generate an &lt;a 2719 - href="https://github.com/nix-community/nixos-generators"&gt;image of my 2720 - current system&lt;/a&gt;.&lt;/p&gt; 2721 - &lt;p&gt;&lt;a href="https://u.peppe.rs/6m.png"&gt;&lt;img 2722 - src="https://u.peppe.rs/6m.png" /&gt;&lt;/a&gt;&lt;/p&gt;</description> 2723 - <link>https://oppi.li/posts/nixOS/</link> 2724 - <pubDate>Tue, 01 Sep 2020 07:08:00 +0000</pubDate> 2725 - <guid>https://oppi.li/posts/nixOS/</guid> 2726 - </item> 2727 - <item> 2728 - <title>Gripes With Go</title> 2729 - <description>&lt;p&gt;You’ve read a lot of posts about the shortcomings of the Go 2730 - programming language, so what’s one more.&lt;/p&gt; 2731 - &lt;ol type="1"&gt; 2732 - &lt;li&gt;&lt;a href="#lack-of-sum-types"&gt;Lack of sum types&lt;/a&gt;&lt;/li&gt; 2733 - &lt;li&gt;&lt;a href="#type-assertions"&gt;Type assertions&lt;/a&gt;&lt;/li&gt; 2734 - &lt;li&gt;&lt;a href="#date-and-time"&gt;Date and Time&lt;/a&gt;&lt;/li&gt; 2735 - &lt;li&gt;&lt;a href="#statements-over-expressions"&gt;Statements over 2736 - Expressions&lt;/a&gt;&lt;/li&gt; 2737 - &lt;li&gt;&lt;a href="#erroring-out-on-unused-variables"&gt;Erroring out on unused 2738 - variables&lt;/a&gt;&lt;/li&gt; 2739 - &lt;li&gt;&lt;a href="#error-handling"&gt;Error handling&lt;/a&gt;&lt;/li&gt; 2740 - &lt;/ol&gt; 2741 - &lt;h3 id="lack-of-sum-types"&gt;Lack of Sum types&lt;/h3&gt; 2742 - &lt;p&gt;A “Sum” type is a data type that can hold one of many states at a 2743 - given time, similar to how a boolean can hold a true or a false, not too 2744 - different from an &lt;code&gt;enum&lt;/code&gt; type in C. Go lacks 2745 - &lt;code&gt;enum&lt;/code&gt; types unfortunately, and you are forced to resort to 2746 - crafting your own substitute.&lt;/p&gt; 2747 - &lt;p&gt;A type to represent gender for example:&lt;/p&gt; 2748 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre class="sourceCode go"&gt;&lt;code class="sourceCode go"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; Gender &lt;span class="dt"&gt;int&lt;/span&gt;&lt;/span&gt; 2749 - &lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2750 - &lt;span id="cb1-3"&gt;&lt;a href="#cb1-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;const&lt;/span&gt; &lt;span class="op"&gt;(&lt;/span&gt;&lt;/span&gt; 2751 - &lt;span id="cb1-4"&gt;&lt;a href="#cb1-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; Male Gender &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="ot"&gt;iota&lt;/span&gt; &lt;span class="co"&gt;// assigns Male to 0&lt;/span&gt;&lt;/span&gt; 2752 - &lt;span id="cb1-5"&gt;&lt;a href="#cb1-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; Female &lt;span class="co"&gt;// assigns Female to 1&lt;/span&gt;&lt;/span&gt; 2753 - &lt;span id="cb1-6"&gt;&lt;a href="#cb1-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; Other &lt;span class="co"&gt;// assigns Other to 2&lt;/span&gt;&lt;/span&gt; 2754 - &lt;span id="cb1-7"&gt;&lt;a href="#cb1-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt; 2755 - &lt;span id="cb1-8"&gt;&lt;a href="#cb1-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2756 - &lt;span id="cb1-9"&gt;&lt;a href="#cb1-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;fmt&lt;span class="op"&gt;.&lt;/span&gt;Println&lt;span class="op"&gt;(&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;My gender is &amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; Male&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt; 2757 - &lt;span id="cb1-10"&gt;&lt;a href="#cb1-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// My gender is 0&lt;/span&gt;&lt;/span&gt; 2758 - &lt;span id="cb1-11"&gt;&lt;a href="#cb1-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// Oops! We have to implement String() for Gender ...&lt;/span&gt;&lt;/span&gt; 2759 - &lt;span id="cb1-12"&gt;&lt;a href="#cb1-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2760 - &lt;span id="cb1-13"&gt;&lt;a href="#cb1-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;func&lt;/span&gt; &lt;span class="op"&gt;(&lt;/span&gt;g Gender&lt;span class="op"&gt;)&lt;/span&gt; String&lt;span class="op"&gt;()&lt;/span&gt; &lt;span class="dt"&gt;string&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2761 - &lt;span id="cb1-14"&gt;&lt;a href="#cb1-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;switch&lt;/span&gt; &lt;span class="op"&gt;(&lt;/span&gt;g&lt;span class="op"&gt;)&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2762 - &lt;span id="cb1-15"&gt;&lt;a href="#cb1-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;case&lt;/span&gt; &lt;span class="dv"&gt;0&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Male&amp;quot;&lt;/span&gt;&lt;/span&gt; 2763 - &lt;span id="cb1-16"&gt;&lt;a href="#cb1-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;case&lt;/span&gt; &lt;span class="dv"&gt;1&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Female&amp;quot;&lt;/span&gt;&lt;/span&gt; 2764 - &lt;span id="cb1-17"&gt;&lt;a href="#cb1-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;default&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;Other&amp;quot;&lt;/span&gt;&lt;/span&gt; 2765 - &lt;span id="cb1-18"&gt;&lt;a href="#cb1-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 2766 - &lt;span id="cb1-19"&gt;&lt;a href="#cb1-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 2767 - &lt;span id="cb1-20"&gt;&lt;a href="#cb1-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2768 - &lt;span id="cb1-21"&gt;&lt;a href="#cb1-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// You can accidentally do stupid stuff like:&lt;/span&gt;&lt;/span&gt; 2769 - &lt;span id="cb1-22"&gt;&lt;a href="#cb1-22" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;gender &lt;span class="op"&gt;:=&lt;/span&gt; Male &lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="dv"&gt;1&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2770 - &lt;p&gt;The Haskell equivalent of the same:&lt;/p&gt; 2771 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 2772 - class="sourceCode haskell"&gt;&lt;code class="sourceCode haskell"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;data&lt;/span&gt; &lt;span class="dt"&gt;Gender&lt;/span&gt; &lt;span class="ot"&gt;=&lt;/span&gt; &lt;span class="dt"&gt;Male&lt;/span&gt;&lt;/span&gt; 2773 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;|&lt;/span&gt; &lt;span class="dt"&gt;Female&lt;/span&gt;&lt;/span&gt; 2774 - &lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;|&lt;/span&gt; &lt;span class="dt"&gt;Other&lt;/span&gt;&lt;/span&gt; 2775 - &lt;span id="cb2-4"&gt;&lt;a href="#cb2-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;deriving&lt;/span&gt; (&lt;span class="dt"&gt;Show&lt;/span&gt;)&lt;/span&gt; 2776 - &lt;span id="cb2-5"&gt;&lt;a href="#cb2-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2777 - &lt;span id="cb2-6"&gt;&lt;a href="#cb2-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;print&lt;/span&gt; &lt;span class="op"&gt;$&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;My gender is &amp;quot;&lt;/span&gt; &lt;span class="op"&gt;++&lt;/span&gt; (&lt;span class="fu"&gt;show&lt;/span&gt; &lt;span class="dt"&gt;Male&lt;/span&gt;)&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2778 - &lt;h3 id="type-assertions"&gt;Type Assertions&lt;/h3&gt; 2779 - &lt;p&gt;A downcast with an optional error check? What could go wrong?&lt;/p&gt; 2780 - &lt;p&gt;Type assertions in Go allow you to do:&lt;/p&gt; 2781 - &lt;div class="sourceCode" id="cb3"&gt;&lt;pre class="sourceCode go"&gt;&lt;code class="sourceCode go"&gt;&lt;span id="cb3-1"&gt;&lt;a href="#cb3-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;var&lt;/span&gt; x &lt;span class="kw"&gt;interface&lt;/span&gt;&lt;span class="op"&gt;{}&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dv"&gt;7&lt;/span&gt;&lt;/span&gt; 2782 - &lt;span id="cb3-2"&gt;&lt;a href="#cb3-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;y&lt;span class="op"&gt;,&lt;/span&gt; goodToGo &lt;span class="op"&gt;:=&lt;/span&gt; x&lt;span class="op"&gt;.(&lt;/span&gt;&lt;span class="dt"&gt;int&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt; 2783 - &lt;span id="cb3-3"&gt;&lt;a href="#cb3-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;if&lt;/span&gt; goodToGo &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2784 - &lt;span id="cb3-4"&gt;&lt;a href="#cb3-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; fmt&lt;span class="op"&gt;.&lt;/span&gt;Println&lt;span class="op"&gt;(&lt;/span&gt;y&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt; 2785 - &lt;span id="cb3-5"&gt;&lt;a href="#cb3-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2786 - &lt;p&gt;The error check however is optional:&lt;/p&gt; 2787 - &lt;div class="sourceCode" id="cb4"&gt;&lt;pre class="sourceCode go"&gt;&lt;code class="sourceCode go"&gt;&lt;span id="cb4-1"&gt;&lt;a href="#cb4-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;var&lt;/span&gt; x &lt;span class="kw"&gt;interface&lt;/span&gt;&lt;span class="op"&gt;{}&lt;/span&gt; &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dv"&gt;7&lt;/span&gt;&lt;/span&gt; 2788 - &lt;span id="cb4-2"&gt;&lt;a href="#cb4-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;var&lt;/span&gt; y &lt;span class="op"&gt;:=&lt;/span&gt; x&lt;span class="op"&gt;.(&lt;/span&gt;&lt;span class="dt"&gt;float64&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt; 2789 - &lt;span id="cb4-3"&gt;&lt;a href="#cb4-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;fmt&lt;span class="op"&gt;.&lt;/span&gt;Println&lt;span class="op"&gt;(&lt;/span&gt;y&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt; 2790 - &lt;span id="cb4-4"&gt;&lt;a href="#cb4-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// results in a runtime error:&lt;/span&gt;&lt;/span&gt; 2791 - &lt;span id="cb4-5"&gt;&lt;a href="#cb4-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// panic: interface conversion: interface {} is int, not float64&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2792 - &lt;h3 id="date-and-time"&gt;Date and Time&lt;/h3&gt; 2793 - &lt;p&gt;Anyone that has written Go previously, will probably already know 2794 - what I am getting at here. For the uninitiated, parsing and formatting 2795 - dates in Go requires a “layout”. This “layout” is based on magical 2796 - reference date:&lt;/p&gt; 2797 - &lt;pre&gt;&lt;code&gt;Mon Jan 2 15:04:05 MST 2006&lt;/code&gt;&lt;/pre&gt; 2798 - &lt;p&gt;Which is the date produced when you write the first seven natural 2799 - numbers like so:&lt;/p&gt; 2800 - &lt;pre&gt;&lt;code&gt;01/02 03:04:05 &amp;#39;06 -0700&lt;/code&gt;&lt;/pre&gt; 2801 - &lt;p&gt;Parsing a string in &lt;code&gt;YYYY-MM-DD&lt;/code&gt; format would look 2802 - something like:&lt;/p&gt; 2803 - &lt;div class="sourceCode" id="cb7"&gt;&lt;pre class="sourceCode go"&gt;&lt;code class="sourceCode go"&gt;&lt;span id="cb7-1"&gt;&lt;a href="#cb7-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;const&lt;/span&gt; layout &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;2006-01-02&amp;quot;&lt;/span&gt;&lt;/span&gt; 2804 - &lt;span id="cb7-2"&gt;&lt;a href="#cb7-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;time&lt;span class="op"&gt;.&lt;/span&gt;Parse&lt;span class="op"&gt;(&lt;/span&gt;layout&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;2020-08-01&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;)&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2805 - &lt;p&gt;This so-called “intuitive” method of formatting dates doesn’t allow 2806 - you to print &lt;code&gt;0000 hrs&lt;/code&gt; as &lt;code&gt;2400 hrs&lt;/code&gt;, it doesn’t 2807 - allow you to omit the leading zero in 24 hour formats. It is rife with 2808 - inconveniences, if only there were a &lt;a 2809 - href="https://man7.org/linux/man-pages/man3/strftime.3.html"&gt;tried and 2810 - tested&lt;/a&gt; date formatting convention …&lt;/p&gt; 2811 - &lt;h3 id="statements-over-expressions"&gt;Statements over Expressions&lt;/h3&gt; 2812 - &lt;p&gt;Statements have side effects, expressions return values. More often 2813 - than not, expressions are easier to understand at a glance: evaluate the 2814 - LHS and assign the same to the RHS.&lt;/p&gt; 2815 - &lt;p&gt;Rust allows you to create local namespaces, and treats blocks 2816 - (&lt;code&gt;{}&lt;/code&gt;) as expressions:&lt;/p&gt; 2817 - &lt;div class="sourceCode" id="cb8"&gt;&lt;pre 2818 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb8-1"&gt;&lt;a href="#cb8-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; twenty_seven &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2819 - &lt;span id="cb8-2"&gt;&lt;a href="#cb8-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; three &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dv"&gt;1&lt;/span&gt; &lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="dv"&gt;2&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2820 - &lt;span id="cb8-3"&gt;&lt;a href="#cb8-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; nine &lt;span class="op"&gt;=&lt;/span&gt; three &lt;span class="op"&gt;*&lt;/span&gt; three&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2821 - &lt;span id="cb8-4"&gt;&lt;a href="#cb8-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; nine &lt;span class="op"&gt;*&lt;/span&gt; three&lt;/span&gt; 2822 - &lt;span id="cb8-5"&gt;&lt;a href="#cb8-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2823 - &lt;p&gt;The Go equivalent of the same:&lt;/p&gt; 2824 - &lt;div class="sourceCode" id="cb9"&gt;&lt;pre class="sourceCode go"&gt;&lt;code class="sourceCode go"&gt;&lt;span id="cb9-1"&gt;&lt;a href="#cb9-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;twenty_seven &lt;span class="op"&gt;:=&lt;/span&gt; &lt;span class="ot"&gt;nil&lt;/span&gt;&lt;/span&gt; 2825 - &lt;span id="cb9-2"&gt;&lt;a href="#cb9-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2826 - &lt;span id="cb9-3"&gt;&lt;a href="#cb9-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;three &lt;span class="op"&gt;:=&lt;/span&gt; &lt;span class="dv"&gt;1&lt;/span&gt; &lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="dv"&gt;2&lt;/span&gt;&lt;/span&gt; 2827 - &lt;span id="cb9-4"&gt;&lt;a href="#cb9-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;nine &lt;span class="op"&gt;:=&lt;/span&gt; three &lt;span class="op"&gt;*&lt;/span&gt; three&lt;/span&gt; 2828 - &lt;span id="cb9-5"&gt;&lt;a href="#cb9-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;twenty_seven &lt;span class="op"&gt;=&lt;/span&gt; nine &lt;span class="op"&gt;*&lt;/span&gt; three&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2829 - &lt;h3 id="erroring-out-on-unused-variables"&gt;Erroring out on unused 2830 - variables&lt;/h3&gt; 2831 - &lt;p&gt;Want to quickly prototype something? Go says no! In all seriousness, 2832 - a warning would suffice, I don’t want to have to go back and comment 2833 - each unused import out, only to come back and uncomment them a few 2834 - seconds later.&lt;/p&gt; 2835 - &lt;h3 id="error-handling"&gt;Error handling&lt;/h3&gt; 2836 - &lt;div class="sourceCode" id="cb10"&gt;&lt;pre 2837 - class="sourceCode go"&gt;&lt;code class="sourceCode go"&gt;&lt;span id="cb10-1"&gt;&lt;a href="#cb10-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;if&lt;/span&gt; err &lt;span class="op"&gt;!=&lt;/span&gt; &lt;span class="ot"&gt;nil&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="op"&gt;...&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2838 - &lt;p&gt;Need I say more? I will, for good measure:&lt;/p&gt; 2839 - &lt;ol type="1"&gt; 2840 - &lt;li&gt;Error handling is optional&lt;/li&gt; 2841 - &lt;li&gt;Errors are propagated via a clunky &lt;code&gt;if&lt;/code&gt; + 2842 - &lt;code&gt;return&lt;/code&gt; statement&lt;/li&gt; 2843 - &lt;/ol&gt; 2844 - &lt;p&gt;I prefer Haskell’s “Monadic” error handling, which is employed by 2845 - Rust as well:&lt;/p&gt; 2846 - &lt;div class="sourceCode" id="cb11"&gt;&lt;pre 2847 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb11-1"&gt;&lt;a href="#cb11-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// 1. error handling is compulsory&lt;/span&gt;&lt;/span&gt; 2848 - &lt;span id="cb11-2"&gt;&lt;a href="#cb11-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// 2. errors are propagated with the `?` operator&lt;/span&gt;&lt;/span&gt; 2849 - &lt;span id="cb11-3"&gt;&lt;a href="#cb11-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; foo() &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;Result&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dt"&gt;String&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="pp"&gt;io::&lt;/span&gt;&lt;span class="bu"&gt;Error&lt;/span&gt;&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2850 - &lt;span id="cb11-4"&gt;&lt;a href="#cb11-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="kw"&gt;mut&lt;/span&gt; f &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;File::&lt;/span&gt;open(&lt;span class="st"&gt;&amp;quot;foo.txt&amp;quot;&lt;/span&gt;)&lt;span class="op"&gt;?;&lt;/span&gt; &lt;span class="co"&gt;// return if error&lt;/span&gt;&lt;/span&gt; 2851 - &lt;span id="cb11-5"&gt;&lt;a href="#cb11-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="kw"&gt;mut&lt;/span&gt; s &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dt"&gt;String&lt;/span&gt;&lt;span class="pp"&gt;::&lt;/span&gt;new()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2852 - &lt;span id="cb11-6"&gt;&lt;a href="#cb11-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2853 - &lt;span id="cb11-7"&gt;&lt;a href="#cb11-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; f&lt;span class="op"&gt;.&lt;/span&gt;read_to_string(&lt;span class="op"&gt;&amp;amp;&lt;/span&gt;&lt;span class="kw"&gt;mut&lt;/span&gt; s)&lt;span class="op"&gt;?;&lt;/span&gt; &lt;span class="co"&gt;// return if error&lt;/span&gt;&lt;/span&gt; 2854 - &lt;span id="cb11-8"&gt;&lt;a href="#cb11-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2855 - &lt;span id="cb11-9"&gt;&lt;a href="#cb11-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cn"&gt;Ok&lt;/span&gt;(s) &lt;span class="co"&gt;// all good, return a string inside a `Result` context&lt;/span&gt;&lt;/span&gt; 2856 - &lt;span id="cb11-10"&gt;&lt;a href="#cb11-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 2857 - &lt;span id="cb11-11"&gt;&lt;a href="#cb11-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 2858 - &lt;span id="cb11-12"&gt;&lt;a href="#cb11-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; main() &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2859 - &lt;span id="cb11-13"&gt;&lt;a href="#cb11-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="co"&gt;// `contents` is an enum known as Result:&lt;/span&gt;&lt;/span&gt; 2860 - &lt;span id="cb11-14"&gt;&lt;a href="#cb11-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; contents &lt;span class="op"&gt;=&lt;/span&gt; foo()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2861 - &lt;span id="cb11-15"&gt;&lt;a href="#cb11-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;match&lt;/span&gt; contents &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2862 - &lt;span id="cb11-16"&gt;&lt;a href="#cb11-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cn"&gt;Ok&lt;/span&gt;(c) &lt;span class="op"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pp"&gt;println!&lt;/span&gt;(c)&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 2863 - &lt;span id="cb11-17"&gt;&lt;a href="#cb11-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cn"&gt;Err&lt;/span&gt;(e) &lt;span class="op"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pp"&gt;eprintln!&lt;/span&gt;(e)&lt;/span&gt; 2864 - &lt;span id="cb11-18"&gt;&lt;a href="#cb11-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 2865 - &lt;span id="cb11-19"&gt;&lt;a href="#cb11-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2866 - &lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt; 2867 - &lt;p&gt;I did not want to conclude without talking about stylistic choices, 2868 - lack of metaprogramming, bizzare export rules, but, I am too busy 2869 - converting my &lt;code&gt;interface{}&lt;/code&gt; types into actual generic code 2870 - for Go v2.&lt;/p&gt;</description> 2871 - <link>https://oppi.li/posts/gripes_with_go/</link> 2872 - <pubDate>Sat, 01 Aug 2020 11:55:00 +0000</pubDate> 2873 - <guid>https://oppi.li/posts/gripes_with_go/</guid> 2874 - </item> 2875 - <item> 2876 - <title>Turing Complete Type Systems</title> 2877 - <description>&lt;p&gt;Rust’s type system is Turing complete:&lt;/p&gt; 2878 - &lt;ul&gt; 2879 - &lt;li&gt;&lt;a href="https://github.com/doctorn/trait-eval/"&gt;FizzBuzz with Rust 2880 - Traits&lt;/a&gt;&lt;/li&gt; 2881 - &lt;li&gt;&lt;a href="https://github.com/Ashymad/fortraith"&gt;A Forth 2882 - implementation with Rust Traits&lt;/a&gt;&lt;/li&gt; 2883 - &lt;/ul&gt; 2884 - &lt;p&gt;It is impossible to determine if a program written in a generally 2885 - Turing complete system will ever stop. That is, it is impossible to 2886 - write a program &lt;code&gt;f&lt;/code&gt; that determines if a program 2887 - &lt;code&gt;g&lt;/code&gt;, where &lt;code&gt;g&lt;/code&gt; is written in a Turing complete 2888 - programming language, will ever halt. The &lt;a 2889 - href="https://en.wikipedia.org/wiki/Halting_problem"&gt;Halting Problem&lt;/a&gt; 2890 - is in fact, an &lt;a 2891 - href="https://en.wikipedia.org/wiki/Undecidable_problem"&gt;undecidable 2892 - problem&lt;/a&gt;.&lt;/p&gt; 2893 - &lt;p&gt;&lt;em&gt;How is any of this relevant?&lt;/em&gt;&lt;/p&gt; 2894 - &lt;p&gt;Rust performs compile-time type inference. The type checker, in turn, 2895 - compiles and infers types, I would describe it as a compiler inside a 2896 - compiler. It is possible that &lt;code&gt;rustc&lt;/code&gt; may never finish 2897 - compiling your Rust program! I lied, &lt;code&gt;rustc&lt;/code&gt; stops after a 2898 - while, after hitting the recursion limit.&lt;/p&gt; 2899 - &lt;p&gt;I understand that this post lacks content.&lt;/p&gt;</description> 2900 - <link>https://oppi.li/posts/turing_complete_type_systems/</link> 2901 - <pubDate>Wed, 17 Jun 2020 18:30:00 +0000</pubDate> 2902 - <guid>https://oppi.li/posts/turing_complete_type_systems/</guid> 2903 - </item> 2904 - <item> 2905 - <title>Auto-currying Rust Functions</title> 2906 - <description>&lt;p&gt;This post contains a gentle introduction to procedural macros in Rust 2907 - and a guide to writing a procedural macro to curry Rust functions. The 2908 - source code for the entire library can be found &lt;a 2909 - href="https://github.com/nerdypepper/cutlass"&gt;here&lt;/a&gt;. It is also 2910 - available on &lt;a 2911 - href="https://crates.io/crates/cutlass"&gt;crates.io&lt;/a&gt;.&lt;/p&gt; 2912 - &lt;p&gt;The following links might prove to be useful before getting 2913 - started:&lt;/p&gt; 2914 - &lt;ul&gt; 2915 - &lt;li&gt;&lt;a 2916 - href="https://doc.rust-lang.org/reference/procedural-macros.html"&gt;Procedural 2917 - Macros&lt;/a&gt;&lt;/li&gt; 2918 - &lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Currying"&gt;Currying&lt;/a&gt;&lt;/li&gt; 2919 - &lt;/ul&gt; 2920 - &lt;p&gt;Or you can pretend you read them, because I have included a primer 2921 - here :)&lt;/p&gt; 2922 - &lt;h3 id="contents"&gt;Contents&lt;/h3&gt; 2923 - &lt;ol type="1"&gt; 2924 - &lt;li&gt;&lt;a href="#currying"&gt;Currying&lt;/a&gt;&lt;br /&gt; 2925 - &lt;/li&gt; 2926 - &lt;li&gt;&lt;a href="#procedural-macros"&gt;Procedural Macros&lt;/a&gt;&lt;br /&gt; 2927 - &lt;/li&gt; 2928 - &lt;li&gt;&lt;a href="#definitions"&gt;Definitions&lt;/a&gt;&lt;br /&gt; 2929 - &lt;/li&gt; 2930 - &lt;li&gt;&lt;a href="#refinement"&gt;Refinement&lt;/a&gt;&lt;br /&gt; 2931 - &lt;/li&gt; 2932 - &lt;li&gt;&lt;a href="#the-in-betweens"&gt;The In-betweens&lt;/a&gt;&lt;br /&gt; 2933 -      5.1 &lt;a href="#dependencies"&gt;Dependencies&lt;/a&gt;&lt;br /&gt; 2934 -      5.2 &lt;a href="#the-attribute-macro"&gt;The attribute macro&lt;/a&gt;&lt;br /&gt; 2935 -      5.3 &lt;a href="#function-body"&gt;Function Body&lt;/a&gt;&lt;br /&gt; 2936 -      5.4 &lt;a href="#function-signature"&gt;Function Signature&lt;/a&gt;&lt;br /&gt; 2937 -      5.5 &lt;a href="#getting-it-together"&gt;Getting it together&lt;/a&gt;&lt;br /&gt; 2938 - &lt;/li&gt; 2939 - &lt;li&gt;&lt;a href="#debugging-and-testing"&gt;Debugging and Testing&lt;/a&gt;&lt;br /&gt; 2940 - &lt;/li&gt; 2941 - &lt;li&gt;&lt;a href="#notes"&gt;Notes&lt;/a&gt;&lt;br /&gt; 2942 - &lt;/li&gt; 2943 - &lt;li&gt;&lt;a href="#conclusion"&gt;Conclusion&lt;/a&gt;&lt;/li&gt; 2944 - &lt;/ol&gt; 2945 - &lt;h3 id="currying"&gt;Currying&lt;/h3&gt; 2946 - &lt;p&gt;Currying is the process of transformation of a function call like 2947 - &lt;code&gt;f(a, b, c)&lt;/code&gt; to &lt;code&gt;f(a)(b)(c)&lt;/code&gt;. A curried function 2948 - returns a concrete value only when it receives all its arguments! If it 2949 - does recieve an insufficient amount of arguments, say 1 of 3, it returns 2950 - a &lt;em&gt;curried function&lt;/em&gt;, that returns after receiving 2 2951 - arguments.&lt;/p&gt; 2952 - &lt;pre&gt;&lt;code&gt;curry(f(a, b, c)) = h(a)(b)(c) 2953 - 2954 - h(x) = g &amp;lt;- curried function that takes upto 2 args (g) 2955 - g(y) = k &amp;lt;- curried function that takes upto 1 arg (k) 2956 - k(z) = v &amp;lt;- a value (v) 2957 - 2958 - Keen readers will conclude the following, 2959 - h(x)(y)(z) = g(y)(z) = k(z) = v&lt;/code&gt;&lt;/pre&gt; 2960 - &lt;p&gt;Mathematically, if &lt;code&gt;f&lt;/code&gt; is a function that takes two 2961 - arguments &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;, such that 2962 - &lt;code&gt;x ϵ X&lt;/code&gt;, and &lt;code&gt;y ϵ Y&lt;/code&gt; , we write it as:&lt;/p&gt; 2963 - &lt;pre&gt;&lt;code&gt;f: (X × Y) -&amp;gt; Z&lt;/code&gt;&lt;/pre&gt; 2964 - &lt;p&gt;where &lt;code&gt;×&lt;/code&gt; denotes the Cartesian product of set 2965 - &lt;code&gt;X&lt;/code&gt; and &lt;code&gt;Y&lt;/code&gt;, and curried &lt;code&gt;f&lt;/code&gt; (denoted 2966 - by &lt;code&gt;h&lt;/code&gt; here) is written as:&lt;/p&gt; 2967 - &lt;pre&gt;&lt;code&gt;h: X -&amp;gt; (Y -&amp;gt; Z)&lt;/code&gt;&lt;/pre&gt; 2968 - &lt;h3 id="procedural-macros"&gt;Procedural Macros&lt;/h3&gt; 2969 - &lt;p&gt;These are functions that take code as input and spit out modified 2970 - code as output. Powerful stuff. Rust has three kinds of proc-macros:&lt;/p&gt; 2971 - &lt;ul&gt; 2972 - &lt;li&gt;Function like macros&lt;br /&gt; 2973 - &lt;/li&gt; 2974 - &lt;li&gt;Derive macros: &lt;code&gt;#[derive(...)]&lt;/code&gt;, used to automatically 2975 - implement traits for structs/enums&lt;br /&gt; 2976 - &lt;/li&gt; 2977 - &lt;li&gt;and Attribute macros: &lt;code&gt;#[test]&lt;/code&gt;, usually slapped onto 2978 - functions&lt;/li&gt; 2979 - &lt;/ul&gt; 2980 - &lt;p&gt;We will be using Attribute macros to convert a Rust function into a 2981 - curried Rust function, which we should be able to call via: 2982 - &lt;code&gt;function(arg1)(arg2)&lt;/code&gt;.&lt;/p&gt; 2983 - &lt;h3 id="definitions"&gt;Definitions&lt;/h3&gt; 2984 - &lt;p&gt;Being respectable programmers, we define the input to and the output 2985 - from our proc-macro. Here’s a good non-trivial function to start out 2986 - with:&lt;/p&gt; 2987 - &lt;div class="sourceCode" id="cb4"&gt;&lt;pre 2988 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb4-1"&gt;&lt;a href="#cb4-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; add(x&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; y&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; z&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2989 - &lt;span id="cb4-2"&gt;&lt;a href="#cb4-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; x &lt;span class="op"&gt;+&lt;/span&gt; y &lt;span class="op"&gt;+&lt;/span&gt; z&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 2990 - &lt;span id="cb4-3"&gt;&lt;a href="#cb4-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 2991 - &lt;p&gt;Hmm, what would our output look like? What should our proc-macro 2992 - generate ideally? Well, if we understood currying correctly, we should 2993 - accept an argument and return a function that accepts an argument and 2994 - returns … you get the point. Something like this should do:&lt;/p&gt; 2995 - &lt;div class="sourceCode" id="cb5"&gt;&lt;pre 2996 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb5-1"&gt;&lt;a href="#cb5-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; add_curried1(x&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="op"&gt;?&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2997 - &lt;span id="cb5-2"&gt;&lt;a href="#cb5-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="kw"&gt;fn&lt;/span&gt; add_curried2 (y&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="op"&gt;?&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2998 - &lt;span id="cb5-3"&gt;&lt;a href="#cb5-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="kw"&gt;fn&lt;/span&gt; add_curried3 (z&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 2999 - &lt;span id="cb5-4"&gt;&lt;a href="#cb5-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; x &lt;span class="op"&gt;+&lt;/span&gt; y &lt;span class="op"&gt;+&lt;/span&gt; z&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3000 - &lt;span id="cb5-5"&gt;&lt;a href="#cb5-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3001 - &lt;span id="cb5-6"&gt;&lt;a href="#cb5-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3002 - &lt;span id="cb5-7"&gt;&lt;a href="#cb5-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3003 - &lt;p&gt;A couple of things to note:&lt;/p&gt; 3004 - &lt;p&gt;&lt;strong&gt;Return types&lt;/strong&gt;&lt;br /&gt; 3005 - We have placed &lt;code&gt;?&lt;/code&gt;s in place of return types. Let’s try to 3006 - fix that. &lt;code&gt;add_curried3&lt;/code&gt; returns the ‘value’, so 3007 - &lt;code&gt;u32&lt;/code&gt; is accurate. &lt;code&gt;add_curried2&lt;/code&gt; returns 3008 - &lt;code&gt;add_curried3&lt;/code&gt;. What is the type of 3009 - &lt;code&gt;add_curried3&lt;/code&gt;? It is a function that takes in a 3010 - &lt;code&gt;u32&lt;/code&gt; and returns a &lt;code&gt;u32&lt;/code&gt;. So a 3011 - &lt;code&gt;fn(u32) -&amp;gt; u32&lt;/code&gt; will do right? No, I’ll explain why in 3012 - the next point, but for now, we will make use of the &lt;code&gt;Fn&lt;/code&gt; 3013 - trait, our return type is &lt;code&gt;impl Fn(u32) -&amp;gt; u32&lt;/code&gt;. This 3014 - basically tells the compiler that we will be returning something 3015 - function-like, a.k.a, behaves like a &lt;code&gt;Fn&lt;/code&gt;. Cool!&lt;/p&gt; 3016 - &lt;p&gt;If you have been following along, you should be able to tell that the 3017 - return type of &lt;code&gt;add_curried1&lt;/code&gt; is:&lt;/p&gt; 3018 - &lt;pre&gt;&lt;code&gt;impl Fn(u32) -&amp;gt; (impl Fn(u32) -&amp;gt; u32)&lt;/code&gt;&lt;/pre&gt; 3019 - &lt;p&gt;We can drop the parentheses because &lt;code&gt;-&amp;gt;&lt;/code&gt; is right 3020 - associative:&lt;/p&gt; 3021 - &lt;pre&gt;&lt;code&gt;impl Fn(u32) -&amp;gt; impl Fn(u32) -&amp;gt; u32 3022 - &lt;/code&gt;&lt;/pre&gt; 3023 - &lt;p&gt;&lt;strong&gt;Accessing environment&lt;/strong&gt;&lt;br /&gt; 3024 - A function cannot access it’s environment. Our solution will not work. 3025 - &lt;code&gt;add_curried3&lt;/code&gt; attempts to access &lt;code&gt;x&lt;/code&gt;, which is 3026 - not allowed! A closure&lt;a href="#fn1" class="footnote-ref" id="fnref1" 3027 - role="doc-noteref"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt; however, can. If we are returning a 3028 - closure, our return type must be &lt;code&gt;impl Fn&lt;/code&gt;, and not 3029 - &lt;code&gt;fn&lt;/code&gt;. The difference between the &lt;code&gt;Fn&lt;/code&gt; trait and 3030 - function pointers is beyond the scope of this post.&lt;/p&gt; 3031 - &lt;h3 id="refinement"&gt;Refinement&lt;/h3&gt; 3032 - &lt;p&gt;Armed with knowledge, we refine our expected output, this time, 3033 - employing closures:&lt;/p&gt; 3034 - &lt;div class="sourceCode" id="cb8"&gt;&lt;pre 3035 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb8-1"&gt;&lt;a href="#cb8-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; add(x&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3036 - &lt;span id="cb8-2"&gt;&lt;a href="#cb8-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="kw"&gt;move&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;y&lt;span class="op"&gt;|&lt;/span&gt; &lt;span class="kw"&gt;move&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;z&lt;span class="op"&gt;|&lt;/span&gt; x &lt;span class="op"&gt;+&lt;/span&gt; y &lt;span class="op"&gt;+&lt;/span&gt; z&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3037 - &lt;span id="cb8-3"&gt;&lt;a href="#cb8-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3038 - &lt;p&gt;Alas, that does not compile either! It errors out with the following 3039 - message:&lt;/p&gt; 3040 - &lt;pre&gt;&lt;code&gt;error[E0562]: `impl Trait` not allowed outside of function 3041 - and inherent method return types 3042 - --&amp;gt; src/main.rs:17:37 3043 - | 3044 - | fn add(x: u32) -&amp;gt; impl Fn(u32) -&amp;gt; impl Fn(u32) -&amp;gt; u32 3045 - | ^^^^^^^^^^^^^^^^^^^ 3046 - &lt;/code&gt;&lt;/pre&gt; 3047 - &lt;p&gt;You are allowed to return an &lt;code&gt;impl Fn&lt;/code&gt; only inside a 3048 - function. We are currently returning it from another return! Or at 3049 - least, that was the most I could make out of the error message.&lt;/p&gt; 3050 - &lt;p&gt;We are going to have to cheat a bit to fix this issue; with type 3051 - aliases and a convenient nightly feature &lt;a href="#fn2" 3052 - class="footnote-ref" id="fnref2" 3053 - role="doc-noteref"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt;:&lt;/p&gt; 3054 - &lt;div class="sourceCode" id="cb10"&gt;&lt;pre 3055 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb10-1"&gt;&lt;a href="#cb10-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;#![&lt;/span&gt;feature&lt;span class="at"&gt;(&lt;/span&gt;type_alias_impl_trait&lt;span class="at"&gt;)]&lt;/span&gt; &lt;span class="co"&gt;// allows us to use `impl Fn` in type aliases!&lt;/span&gt;&lt;/span&gt; 3056 - &lt;span id="cb10-2"&gt;&lt;a href="#cb10-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3057 - &lt;span id="cb10-3"&gt;&lt;a href="#cb10-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T0 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// the return value when zero args are to be applied&lt;/span&gt;&lt;/span&gt; 3058 - &lt;span id="cb10-4"&gt;&lt;a href="#cb10-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T1 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; T0&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// the return value when one arg is to be applied&lt;/span&gt;&lt;/span&gt; 3059 - &lt;span id="cb10-5"&gt;&lt;a href="#cb10-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T2 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; T1&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// the return value when two args are to be applied&lt;/span&gt;&lt;/span&gt; 3060 - &lt;span id="cb10-6"&gt;&lt;a href="#cb10-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3061 - &lt;span id="cb10-7"&gt;&lt;a href="#cb10-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; add(x&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; T2 &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3062 - &lt;span id="cb10-8"&gt;&lt;a href="#cb10-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="kw"&gt;move&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;y&lt;span class="op"&gt;|&lt;/span&gt; &lt;span class="kw"&gt;move&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;z&lt;span class="op"&gt;|&lt;/span&gt; x &lt;span class="op"&gt;+&lt;/span&gt; y &lt;span class="op"&gt;+&lt;/span&gt; z&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3063 - &lt;span id="cb10-9"&gt;&lt;a href="#cb10-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3064 - &lt;p&gt;Drop that into a cargo project, call &lt;code&gt;add(4)(5)(6)&lt;/code&gt;, cross 3065 - your fingers, and run &lt;code&gt;cargo +nightly run&lt;/code&gt;. You should see a 3066 - 15 unless you forgot to print it!&lt;/p&gt; 3067 - &lt;h3 id="the-in-betweens"&gt;The In-Betweens&lt;/h3&gt; 3068 - &lt;p&gt;Let us write the magical bits that take us from function to curried 3069 - function.&lt;/p&gt; 3070 - &lt;p&gt;Initialize your workspace with &lt;code&gt;cargo new --lib currying&lt;/code&gt;. 3071 - Proc-macro crates are libraries with exactly one export, the macro 3072 - itself. Add a &lt;code&gt;tests&lt;/code&gt; directory to your crate root. Your 3073 - directory should look something like this:&lt;/p&gt; 3074 - &lt;pre&gt;&lt;code&gt;. 3075 - ├── Cargo.toml 3076 - ├── src 3077 - │   └── lib.rs 3078 - └── tests 3079 - └── smoke.rs&lt;/code&gt;&lt;/pre&gt; 3080 - &lt;h4 id="dependencies"&gt;Dependencies&lt;/h4&gt; 3081 - &lt;p&gt;We will be using a total of 3 external crates:&lt;/p&gt; 3082 - &lt;ul&gt; 3083 - &lt;li&gt;&lt;a 3084 - href="https://docs.rs/proc-macro2/1.0.12/proc_macro2/"&gt;proc_macro2&lt;/a&gt;&lt;/li&gt; 3085 - &lt;li&gt;&lt;a href="https://docs.rs/syn/1.0.18/syn/index.html"&gt;syn&lt;/a&gt;&lt;/li&gt; 3086 - &lt;li&gt;&lt;a 3087 - href="https://docs.rs/quote/1.0.4/quote/index.html"&gt;quote&lt;/a&gt;&lt;/li&gt; 3088 - &lt;/ul&gt; 3089 - &lt;p&gt;Here’s a sample &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;/p&gt; 3090 - &lt;pre&gt;&lt;code&gt;# Cargo.toml 3091 - 3092 - [dependencies] 3093 - proc-macro2 = &amp;quot;1.0.9&amp;quot; 3094 - quote = &amp;quot;1.0&amp;quot; 3095 - 3096 - [dependencies.syn] 3097 - version = &amp;quot;1.0&amp;quot; 3098 - features = [&amp;quot;full&amp;quot;] 3099 - 3100 - [lib] 3101 - proc-macro = true # this is important!&lt;/code&gt;&lt;/pre&gt; 3102 - &lt;p&gt;We will be using an external &lt;code&gt;proc-macro2&lt;/code&gt; crate as well 3103 - as an internal &lt;code&gt;proc-macro&lt;/code&gt; crate. Not confusing at all!&lt;/p&gt; 3104 - &lt;h4 id="the-attribute-macro"&gt;The attribute macro&lt;/h4&gt; 3105 - &lt;p&gt;Drop this into &lt;code&gt;src/lib.rs&lt;/code&gt;, to get the ball rolling.&lt;/p&gt; 3106 - &lt;div class="sourceCode" id="cb13"&gt;&lt;pre 3107 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb13-1"&gt;&lt;a href="#cb13-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// src/lib.rs&lt;/span&gt;&lt;/span&gt; 3108 - &lt;span id="cb13-2"&gt;&lt;a href="#cb13-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3109 - &lt;span id="cb13-3"&gt;&lt;a href="#cb13-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;proc_macro::&lt;/span&gt;TokenStream&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// 1&lt;/span&gt;&lt;/span&gt; 3110 - &lt;span id="cb13-4"&gt;&lt;a href="#cb13-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;quote::&lt;/span&gt;quote&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3111 - &lt;span id="cb13-5"&gt;&lt;a href="#cb13-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;syn::&lt;/span&gt;&lt;span class="op"&gt;{&lt;/span&gt;parse_macro_input&lt;span class="op"&gt;,&lt;/span&gt; ItemFn&lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 3112 - &lt;span id="cb13-6"&gt;&lt;a href="#cb13-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3113 - &lt;span id="cb13-7"&gt;&lt;a href="#cb13-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;#[&lt;/span&gt;proc_macro_attribute&lt;span class="at"&gt;]&lt;/span&gt; &lt;span class="co"&gt;// 2&lt;/span&gt;&lt;/span&gt; 3114 - &lt;span id="cb13-8"&gt;&lt;a href="#cb13-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;pub&lt;/span&gt; &lt;span class="kw"&gt;fn&lt;/span&gt; curry(_attr&lt;span class="op"&gt;:&lt;/span&gt; TokenStream&lt;span class="op"&gt;,&lt;/span&gt; item&lt;span class="op"&gt;:&lt;/span&gt; TokenStream) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; TokenStream &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3115 - &lt;span id="cb13-9"&gt;&lt;a href="#cb13-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; parsed &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;parse_macro_input!&lt;/span&gt;(item &lt;span class="kw"&gt;as&lt;/span&gt; ItemFn)&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// 3&lt;/span&gt;&lt;/span&gt; 3116 - &lt;span id="cb13-10"&gt;&lt;a href="#cb13-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; generate_curry(parsed)&lt;span class="op"&gt;.&lt;/span&gt;into() &lt;span class="co"&gt;// 4&lt;/span&gt;&lt;/span&gt; 3117 - &lt;span id="cb13-11"&gt;&lt;a href="#cb13-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3118 - &lt;span id="cb13-12"&gt;&lt;a href="#cb13-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3119 - &lt;span id="cb13-13"&gt;&lt;a href="#cb13-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; generate_curry(parsed&lt;span class="op"&gt;:&lt;/span&gt; ItemFn) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="pp"&gt;proc_macro2::&lt;/span&gt;TokenStream &lt;span class="op"&gt;{}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3120 - &lt;p&gt;&lt;strong&gt;1. Imports&lt;/strong&gt;&lt;/p&gt; 3121 - &lt;p&gt;A &lt;code&gt;Tokenstream&lt;/code&gt; holds (hopefully valid) Rust code, this is 3122 - the type of our input and output. Note that we are importing this type 3123 - from &lt;code&gt;proc_macro&lt;/code&gt; and not &lt;code&gt;proc_macro2&lt;/code&gt;.&lt;/p&gt; 3124 - &lt;p&gt;&lt;code&gt;quote!&lt;/code&gt; from the &lt;code&gt;quote&lt;/code&gt; crate is a macro that 3125 - allows us to quickly produce &lt;code&gt;TokenStream&lt;/code&gt;s. Much like the 3126 - LISP &lt;code&gt;quote&lt;/code&gt; procedure, you can use the &lt;code&gt;quote!&lt;/code&gt; 3127 - macro for symbolic transformations.&lt;/p&gt; 3128 - &lt;p&gt;&lt;code&gt;ItemFn&lt;/code&gt; from the &lt;code&gt;syn&lt;/code&gt; crate holds the parsed 3129 - &lt;code&gt;TokenStream&lt;/code&gt; of a Rust function. 3130 - &lt;code&gt;parse_macro_input!&lt;/code&gt; is a helper macro provided by 3131 - &lt;code&gt;syn&lt;/code&gt;.&lt;/p&gt; 3132 - &lt;p&gt;&lt;strong&gt;2. The lone export&lt;/strong&gt;&lt;/p&gt; 3133 - &lt;p&gt;Annotate the only &lt;code&gt;pub&lt;/code&gt; of our crate with 3134 - &lt;code&gt;#[proc_macro_attribute]&lt;/code&gt;. This tells rustc that 3135 - &lt;code&gt;curry&lt;/code&gt; is a procedural macro, and allows us to use it as 3136 - &lt;code&gt;#[crate_name::curry]&lt;/code&gt; in other crates. Note the signature of 3137 - the &lt;code&gt;curry&lt;/code&gt; function. &lt;code&gt;_attr&lt;/code&gt; is the 3138 - &lt;code&gt;TokenStream&lt;/code&gt; representing the attribute itself, 3139 - &lt;code&gt;item&lt;/code&gt; refers to the thing we slapped our macro into, in this 3140 - case a function (like &lt;code&gt;add&lt;/code&gt;). The return value is a modified 3141 - &lt;code&gt;TokenStream&lt;/code&gt;, this will contain our curried version of 3142 - &lt;code&gt;add&lt;/code&gt;.&lt;/p&gt; 3143 - &lt;p&gt;&lt;strong&gt;3. The helper macro&lt;/strong&gt;&lt;/p&gt; 3144 - &lt;p&gt;A &lt;code&gt;TokenStream&lt;/code&gt; is a little hard to work with, which is 3145 - why we have the &lt;code&gt;syn&lt;/code&gt; crate, which provides types to 3146 - represent Rust tokens. An &lt;code&gt;RArrow&lt;/code&gt; struct to represent the 3147 - return arrow on a function and so on. One of those types is 3148 - &lt;code&gt;ItemFn&lt;/code&gt;, that represents an entire Rust function. The 3149 - &lt;code&gt;parse_macro_input!&lt;/code&gt; automatically puts the input to our 3150 - macro into an &lt;code&gt;ItemFn&lt;/code&gt;. What a gentleman!&lt;/p&gt; 3151 - &lt;p&gt;&lt;strong&gt;4. Returning &lt;code&gt;TokenStream&lt;/code&gt;s &lt;/strong&gt;&lt;/p&gt; 3152 - &lt;p&gt;We haven’t filled in &lt;code&gt;generate_curry&lt;/code&gt; yet, but we can see 3153 - that it returns a &lt;code&gt;proc_macro2::TokenStream&lt;/code&gt; and not a 3154 - &lt;code&gt;proc_macro::TokenStream&lt;/code&gt;, so drop a &lt;code&gt;.into()&lt;/code&gt; to 3155 - convert it.&lt;/p&gt; 3156 - &lt;p&gt;Lets move on, and fill in &lt;code&gt;generate_curry&lt;/code&gt;, I would 3157 - suggest keeping the documentation for &lt;a 3158 - href="https://docs.rs/syn/1.0.19/syn/struct.ItemFn.html"&gt;&lt;code&gt;syn::ItemFn&lt;/code&gt;&lt;/a&gt; 3159 - and &lt;a 3160 - href="https://docs.rs/syn/1.0.19/syn/struct.Signature.html"&gt;&lt;code&gt;syn::Signature&lt;/code&gt;&lt;/a&gt; 3161 - open.&lt;/p&gt; 3162 - &lt;div class="sourceCode" id="cb14"&gt;&lt;pre 3163 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb14-1"&gt;&lt;a href="#cb14-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// src/lib.rs&lt;/span&gt;&lt;/span&gt; 3164 - &lt;span id="cb14-2"&gt;&lt;a href="#cb14-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3165 - &lt;span id="cb14-3"&gt;&lt;a href="#cb14-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; generate_curry(parsed&lt;span class="op"&gt;:&lt;/span&gt; ItemFn) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="pp"&gt;proc_macro2::&lt;/span&gt;TokenStream &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3166 - &lt;span id="cb14-4"&gt;&lt;a href="#cb14-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_body &lt;span class="op"&gt;=&lt;/span&gt; parsed&lt;span class="op"&gt;.&lt;/span&gt;block&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// function body&lt;/span&gt;&lt;/span&gt; 3167 - &lt;span id="cb14-5"&gt;&lt;a href="#cb14-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; sig &lt;span class="op"&gt;=&lt;/span&gt; parsed&lt;span class="op"&gt;.&lt;/span&gt;sig&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// function signature&lt;/span&gt;&lt;/span&gt; 3168 - &lt;span id="cb14-6"&gt;&lt;a href="#cb14-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; vis &lt;span class="op"&gt;=&lt;/span&gt; parsed&lt;span class="op"&gt;.&lt;/span&gt;vis&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// visibility, pub or not&lt;/span&gt;&lt;/span&gt; 3169 - &lt;span id="cb14-7"&gt;&lt;a href="#cb14-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_name &lt;span class="op"&gt;=&lt;/span&gt; sig&lt;span class="op"&gt;.&lt;/span&gt;ident&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// function name/identifier&lt;/span&gt;&lt;/span&gt; 3170 - &lt;span id="cb14-8"&gt;&lt;a href="#cb14-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_args &lt;span class="op"&gt;=&lt;/span&gt; sig&lt;span class="op"&gt;.&lt;/span&gt;inputs&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// comma separated args&lt;/span&gt;&lt;/span&gt; 3171 - &lt;span id="cb14-9"&gt;&lt;a href="#cb14-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_return_type &lt;span class="op"&gt;=&lt;/span&gt; sig&lt;span class="op"&gt;.&lt;/span&gt;output&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// return type&lt;/span&gt;&lt;/span&gt; 3172 - &lt;span id="cb14-10"&gt;&lt;a href="#cb14-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3173 - &lt;p&gt;We are simply extracting the bits of the function, we will be reusing 3174 - the original function’s visibility and name. Take a look at what 3175 - &lt;code&gt;syn::Signature&lt;/code&gt; can tell us about a function:&lt;/p&gt; 3176 - &lt;pre&gt;&lt;code&gt; .-- syn::Ident (ident) 3177 - / 3178 - fn add(x: u32, y: u32) -&amp;gt; u32 3179 - (fn_token) / ~~~~~~~,~~~~~~ ~~~~~~ 3180 - syn::token::Fn --&amp;#39; / \ (output) 3181 - &amp;#39; `- syn::ReturnType 3182 - Punctuated&amp;lt;FnArg, Comma&amp;gt; (inputs)&lt;/code&gt;&lt;/pre&gt; 3183 - &lt;p&gt;Enough analysis, lets produce our first bit of Rust code.&lt;/p&gt; 3184 - &lt;h4 id="function-body"&gt;Function Body&lt;/h4&gt; 3185 - &lt;p&gt;Recall that the body of a curried &lt;code&gt;add&lt;/code&gt; should look like 3186 - this:&lt;/p&gt; 3187 - &lt;div class="sourceCode" id="cb16"&gt;&lt;pre 3188 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb16-1"&gt;&lt;a href="#cb16-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="kw"&gt;move&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;y&lt;span class="op"&gt;|&lt;/span&gt; &lt;span class="kw"&gt;move&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;z&lt;span class="op"&gt;|&lt;/span&gt; x &lt;span class="op"&gt;+&lt;/span&gt; y &lt;span class="op"&gt;+&lt;/span&gt; z&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3189 - &lt;p&gt;And in general:&lt;/p&gt; 3190 - &lt;div class="sourceCode" id="cb17"&gt;&lt;pre 3191 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb17-1"&gt;&lt;a href="#cb17-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="kw"&gt;move&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;arg2&lt;span class="op"&gt;|&lt;/span&gt; &lt;span class="kw"&gt;move&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;arg3&lt;span class="op"&gt;|&lt;/span&gt; &lt;span class="op"&gt;...&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;argN&lt;span class="op"&gt;|&lt;/span&gt; &lt;span class="op"&gt;&amp;lt;&lt;/span&gt;function body here&lt;span class="op"&gt;&amp;gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3192 - &lt;p&gt;We already have the function’s body, provided by 3193 - &lt;code&gt;fn_body&lt;/code&gt;, in our &lt;code&gt;generate_curry&lt;/code&gt; function. All 3194 - that’s left to add is the &lt;code&gt;move |arg2| move |arg3| ...&lt;/code&gt; 3195 - stuff, for which we need to extract the argument identifiers (doc: &lt;a 3196 - href="https://docs.rs/syn/1.0.18/syn/punctuated/struct.Punctuated.html"&gt;Punctuated&lt;/a&gt;, 3197 - &lt;a href="https://docs.rs/syn/1.0.18/syn/enum.FnArg.html"&gt;FnArg&lt;/a&gt;, &lt;a 3198 - href="https://docs.rs/syn/1.0.18/syn/struct.PatType.html"&gt;PatType&lt;/a&gt;):&lt;/p&gt; 3199 - &lt;div class="sourceCode" id="cb18"&gt;&lt;pre 3200 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb18-1"&gt;&lt;a href="#cb18-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// src/lib.rs&lt;/span&gt;&lt;/span&gt; 3201 - &lt;span id="cb18-2"&gt;&lt;a href="#cb18-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;syn::punctuated::&lt;/span&gt;Punctuated&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3202 - &lt;span id="cb18-3"&gt;&lt;a href="#cb18-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;syn::&lt;/span&gt;&lt;span class="op"&gt;{&lt;/span&gt;parse_macro_input&lt;span class="op"&gt;,&lt;/span&gt; FnArg&lt;span class="op"&gt;,&lt;/span&gt; Pat&lt;span class="op"&gt;,&lt;/span&gt; ItemFn&lt;span class="op"&gt;,&lt;/span&gt; Block&lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 3203 - &lt;span id="cb18-4"&gt;&lt;a href="#cb18-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3204 - &lt;span id="cb18-5"&gt;&lt;a href="#cb18-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; extract_arg_idents(fn_args&lt;span class="op"&gt;:&lt;/span&gt; Punctuated&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;FnArg&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="pp"&gt;syn::token::&lt;/span&gt;Comma&lt;span class="op"&gt;&amp;gt;&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;Vec&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dt"&gt;Box&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;Pat&lt;span class="op"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt; &lt;/span&gt; 3205 - &lt;span id="cb18-6"&gt;&lt;a href="#cb18-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; fn_args&lt;span class="op"&gt;.&lt;/span&gt;into_iter()&lt;span class="op"&gt;.&lt;/span&gt;map(extract_arg_pat)&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="pp"&gt;collect::&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dt"&gt;Vec&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;_&lt;span class="op"&gt;&amp;gt;&amp;gt;&lt;/span&gt;()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3206 - &lt;span id="cb18-7"&gt;&lt;a href="#cb18-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3207 - &lt;p&gt;Alright, so we are iterating over function args 3208 - (&lt;code&gt;Punctuated&lt;/code&gt; is a collection that you can iterate over) and 3209 - mapping an &lt;code&gt;extract_arg_pat&lt;/code&gt; to every item. What’s 3210 - &lt;code&gt;extract_arg_pat&lt;/code&gt;?&lt;/p&gt; 3211 - &lt;div class="sourceCode" id="cb19"&gt;&lt;pre 3212 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb19-1"&gt;&lt;a href="#cb19-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// src/lib.rs&lt;/span&gt;&lt;/span&gt; 3213 - &lt;span id="cb19-2"&gt;&lt;a href="#cb19-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3214 - &lt;span id="cb19-3"&gt;&lt;a href="#cb19-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; extract_arg_pat(a&lt;span class="op"&gt;:&lt;/span&gt; FnArg) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;Box&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;Pat&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3215 - &lt;span id="cb19-4"&gt;&lt;a href="#cb19-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;match&lt;/span&gt; a &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3216 - &lt;span id="cb19-5"&gt;&lt;a href="#cb19-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;FnArg::&lt;/span&gt;Typed(p) &lt;span class="op"&gt;=&amp;gt;&lt;/span&gt; p&lt;span class="op"&gt;.&lt;/span&gt;pat&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3217 - &lt;span id="cb19-6"&gt;&lt;a href="#cb19-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; _ &lt;span class="op"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pp"&gt;panic!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;Not supported on types with `self`!&amp;quot;&lt;/span&gt;)&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3218 - &lt;span id="cb19-7"&gt;&lt;a href="#cb19-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3219 - &lt;span id="cb19-8"&gt;&lt;a href="#cb19-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3220 - &lt;p&gt;&lt;code&gt;FnArg&lt;/code&gt; is an enum type as you might have guessed. The 3221 - &lt;code&gt;Typed&lt;/code&gt; variant encompasses args that are written as 3222 - &lt;code&gt;name: type&lt;/code&gt; and the other variant, &lt;code&gt;Reciever&lt;/code&gt; 3223 - refers to &lt;code&gt;self&lt;/code&gt; types. Ignore those for now, keep it 3224 - simple.&lt;/p&gt; 3225 - &lt;p&gt;Every &lt;code&gt;FnArg::Typed&lt;/code&gt; value contains a &lt;code&gt;pat&lt;/code&gt;, 3226 - which is in essence, the name of the argument. The type of the arg is 3227 - accessible via &lt;code&gt;p.ty&lt;/code&gt; (we will be using this later).&lt;/p&gt; 3228 - &lt;p&gt;With that done, we should be able to write the codegen for the 3229 - function body:&lt;/p&gt; 3230 - &lt;div class="sourceCode" id="cb20"&gt;&lt;pre 3231 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb20-1"&gt;&lt;a href="#cb20-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// src/lib.rs&lt;/span&gt;&lt;/span&gt; 3232 - &lt;span id="cb20-2"&gt;&lt;a href="#cb20-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3233 - &lt;span id="cb20-3"&gt;&lt;a href="#cb20-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; generate_body(fn_args&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;[&lt;span class="dt"&gt;Box&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;Pat&lt;span class="op"&gt;&amp;gt;&lt;/span&gt;]&lt;span class="op"&gt;,&lt;/span&gt; body&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;Box&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;Block&lt;span class="op"&gt;&amp;gt;&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="pp"&gt;proc_macro2::&lt;/span&gt;TokenStream &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3234 - &lt;span id="cb20-4"&gt;&lt;a href="#cb20-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;quote!&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3235 - &lt;span id="cb20-5"&gt;&lt;a href="#cb20-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; #( &lt;span class="kw"&gt;move&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;#fn_args&lt;span class="op"&gt;|&lt;/span&gt; )&lt;span class="op"&gt;*&lt;/span&gt; #body&lt;/span&gt; 3236 - &lt;span id="cb20-6"&gt;&lt;a href="#cb20-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3237 - &lt;span id="cb20-7"&gt;&lt;a href="#cb20-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3238 - &lt;p&gt;That is some scary looking syntax! Allow me to explain. The 3239 - &lt;code&gt;quote!{ ... }&lt;/code&gt; returns a 3240 - &lt;code&gt;proc_macro2::TokenStream&lt;/code&gt;, if we wrote 3241 - &lt;code&gt;quote!{ let x = 1 + 2; }&lt;/code&gt;, it wouldn’t create a new variable 3242 - &lt;code&gt;x&lt;/code&gt; with value 3, it would literally produce a stream of 3243 - tokens with that expression.&lt;/p&gt; 3244 - &lt;p&gt;The &lt;code&gt;#&lt;/code&gt; enables variable interpolation. &lt;code&gt;#body&lt;/code&gt; 3245 - will look for &lt;code&gt;body&lt;/code&gt; in the current scope, take its value, 3246 - and insert it in the returned &lt;code&gt;TokenStream&lt;/code&gt;. Kinda like quasi 3247 - quoting in LISPs, you have written one.&lt;/p&gt; 3248 - &lt;p&gt;What about &lt;code&gt;#( move |#fn_args| )*&lt;/code&gt;? That is repetition. 3249 - &lt;code&gt;quote&lt;/code&gt; iterates through &lt;code&gt;fn_args&lt;/code&gt;, and drops a 3250 - &lt;code&gt;move&lt;/code&gt; behind each one, it then places pipes 3251 - (&lt;code&gt;|&lt;/code&gt;), around it.&lt;/p&gt; 3252 - &lt;p&gt;Let us test our first bit of codegen! Modify 3253 - &lt;code&gt;generate_curry&lt;/code&gt; like so:&lt;/p&gt; 3254 - &lt;div class="sourceCode" id="cb21"&gt;&lt;pre 3255 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb21-1"&gt;&lt;a href="#cb21-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// src/lib.rs&lt;/span&gt;&lt;/span&gt; 3256 - &lt;span id="cb21-2"&gt;&lt;a href="#cb21-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3257 - &lt;span id="cb21-3"&gt;&lt;a href="#cb21-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;fn&lt;/span&gt; generate_curry(parsed&lt;span class="op"&gt;:&lt;/span&gt; ItemFn) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; TokenStream &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3258 - &lt;span id="cb21-4"&gt;&lt;a href="#cb21-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_body &lt;span class="op"&gt;=&lt;/span&gt; parsed&lt;span class="op"&gt;.&lt;/span&gt;block&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3259 - &lt;span id="cb21-5"&gt;&lt;a href="#cb21-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; sig &lt;span class="op"&gt;=&lt;/span&gt; parsed&lt;span class="op"&gt;.&lt;/span&gt;sig&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3260 - &lt;span id="cb21-6"&gt;&lt;a href="#cb21-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; vis &lt;span class="op"&gt;=&lt;/span&gt; parsed&lt;span class="op"&gt;.&lt;/span&gt;vis&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3261 - &lt;span id="cb21-7"&gt;&lt;a href="#cb21-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_name &lt;span class="op"&gt;=&lt;/span&gt; sig&lt;span class="op"&gt;.&lt;/span&gt;ident&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3262 - &lt;span id="cb21-8"&gt;&lt;a href="#cb21-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_args &lt;span class="op"&gt;=&lt;/span&gt; sig&lt;span class="op"&gt;.&lt;/span&gt;inputs&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3263 - &lt;span id="cb21-9"&gt;&lt;a href="#cb21-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_return_type &lt;span class="op"&gt;=&lt;/span&gt; sig&lt;span class="op"&gt;.&lt;/span&gt;output&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3264 - &lt;span id="cb21-10"&gt;&lt;a href="#cb21-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3265 - &lt;span id="cb21-11"&gt;&lt;a href="#cb21-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="kw"&gt;let&lt;/span&gt; arg_idents &lt;span class="op"&gt;=&lt;/span&gt; extract_arg_idents(fn_args&lt;span class="op"&gt;.&lt;/span&gt;clone())&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3266 - &lt;span id="cb21-12"&gt;&lt;a href="#cb21-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="kw"&gt;let&lt;/span&gt; first_ident &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;arg_idents&lt;span class="op"&gt;.&lt;/span&gt;first()&lt;span class="op"&gt;.&lt;/span&gt;unwrap()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3267 - &lt;span id="cb21-13"&gt;&lt;a href="#cb21-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3268 - &lt;span id="cb21-14"&gt;&lt;a href="#cb21-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="co"&gt;// remember, our curried body starts with the second argument!&lt;/span&gt;&lt;/span&gt; 3269 - &lt;span id="cb21-15"&gt;&lt;a href="#cb21-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="kw"&gt;let&lt;/span&gt; curried_body &lt;span class="op"&gt;=&lt;/span&gt; generate_body(&lt;span class="op"&gt;&amp;amp;&lt;/span&gt;arg_idents[&lt;span class="dv"&gt;1&lt;/span&gt;&lt;span class="op"&gt;..&lt;/span&gt;]&lt;span class="op"&gt;,&lt;/span&gt; fn_body&lt;span class="op"&gt;.&lt;/span&gt;clone())&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3270 - &lt;span id="cb21-16"&gt;&lt;a href="#cb21-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="pp"&gt;println!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;{}&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; curried_body)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3271 - &lt;span id="cb21-17"&gt;&lt;a href="#cb21-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3272 - &lt;span id="cb21-18"&gt;&lt;a href="#cb21-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="pp"&gt;TokenStream::&lt;/span&gt;new()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3273 - &lt;span id="cb21-19"&gt;&lt;a href="#cb21-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3274 - &lt;p&gt;Add a little test to &lt;code&gt;tests/&lt;/code&gt;:&lt;/p&gt; 3275 - &lt;div class="sourceCode" id="cb22"&gt;&lt;pre 3276 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb22-1"&gt;&lt;a href="#cb22-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// tests/smoke.rs&lt;/span&gt;&lt;/span&gt; 3277 - &lt;span id="cb22-2"&gt;&lt;a href="#cb22-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3278 - &lt;span id="cb22-3"&gt;&lt;a href="#cb22-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;#[&lt;/span&gt;&lt;span class="pp"&gt;currying::&lt;/span&gt;curry&lt;span class="at"&gt;]&lt;/span&gt;&lt;/span&gt; 3279 - &lt;span id="cb22-4"&gt;&lt;a href="#cb22-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; add(x&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; y&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; z&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3280 - &lt;span id="cb22-5"&gt;&lt;a href="#cb22-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; x &lt;span class="op"&gt;+&lt;/span&gt; y &lt;span class="op"&gt;+&lt;/span&gt; z&lt;/span&gt; 3281 - &lt;span id="cb22-6"&gt;&lt;a href="#cb22-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3282 - &lt;span id="cb22-7"&gt;&lt;a href="#cb22-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3283 - &lt;span id="cb22-8"&gt;&lt;a href="#cb22-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;#[&lt;/span&gt;test&lt;span class="at"&gt;]&lt;/span&gt;&lt;/span&gt; 3284 - &lt;span id="cb22-9"&gt;&lt;a href="#cb22-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; works() &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3285 - &lt;span id="cb22-10"&gt;&lt;a href="#cb22-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;assert!&lt;/span&gt;(&lt;span class="cn"&gt;true&lt;/span&gt;)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3286 - &lt;span id="cb22-11"&gt;&lt;a href="#cb22-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3287 - &lt;p&gt;You should find something like this in the output of 3288 - &lt;code&gt;cargo test&lt;/code&gt;:&lt;/p&gt; 3289 - &lt;pre&gt;&lt;code&gt;return move | y | move | z | { x + y + z }&lt;/code&gt;&lt;/pre&gt; 3290 - &lt;p&gt;Glorious &lt;code&gt;println!&lt;/code&gt; debugging!&lt;/p&gt; 3291 - &lt;h4 id="function-signature"&gt;Function signature&lt;/h4&gt; 3292 - &lt;p&gt;This section gets into the more complicated bits of the macro, 3293 - generating type aliases and the function signature. By the end of this 3294 - section, we should have a full working auto-currying macro!&lt;/p&gt; 3295 - &lt;p&gt;Recall what our generated type aliases should look like, for our 3296 - &lt;code&gt;add&lt;/code&gt; function:&lt;/p&gt; 3297 - &lt;div class="sourceCode" id="cb24"&gt;&lt;pre 3298 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb24-1"&gt;&lt;a href="#cb24-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T0 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3299 - &lt;span id="cb24-2"&gt;&lt;a href="#cb24-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T1 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; T0&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3300 - &lt;span id="cb24-3"&gt;&lt;a href="#cb24-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T2 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; T1&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3301 - &lt;p&gt;In general:&lt;/p&gt; 3302 - &lt;div class="sourceCode" id="cb25"&gt;&lt;pre 3303 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb25-1"&gt;&lt;a href="#cb25-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T0 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="kw"&gt;type&lt;/span&gt;&amp;gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3304 - &lt;span id="cb25-2"&gt;&lt;a href="#cb25-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T1 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kw"&gt;type&lt;/span&gt; of arg N&amp;gt;) -&amp;gt; T0&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3305 - &lt;span id="cb25-3"&gt;&lt;a href="#cb25-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T2 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kw"&gt;type&lt;/span&gt; of arg N - 1&amp;gt;) -&amp;gt; T1&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3306 - &lt;span id="cb25-4"&gt;&lt;a href="#cb25-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;/span&gt; 3307 - &lt;span id="cb25-5"&gt;&lt;a href="#cb25-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;/span&gt; 3308 - &lt;span id="cb25-6"&gt;&lt;a href="#cb25-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;.&lt;/span&gt;&lt;/span&gt; 3309 - &lt;span id="cb25-7"&gt;&lt;a href="#cb25-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T(N-1) &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kw"&gt;type&lt;/span&gt; of arg 2&amp;gt;) -&amp;gt; T(N-2)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3310 - &lt;p&gt;To codegen that, we need the types of:&lt;/p&gt; 3311 - &lt;ul&gt; 3312 - &lt;li&gt;all our inputs (arguments)&lt;/li&gt; 3313 - &lt;li&gt;the output (the return type)&lt;/li&gt; 3314 - &lt;/ul&gt; 3315 - &lt;p&gt;To fetch the types of all our inputs, we can simply reuse the bits we 3316 - wrote to fetch the names of all our inputs! (doc: &lt;a 3317 - href="https://docs.rs/syn/1.0.18/syn/enum.Type.html"&gt;Type&lt;/a&gt;)&lt;/p&gt; 3318 - &lt;div class="sourceCode" id="cb26"&gt;&lt;pre 3319 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb26-1"&gt;&lt;a href="#cb26-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// src/lib.rs&lt;/span&gt;&lt;/span&gt; 3320 - &lt;span id="cb26-2"&gt;&lt;a href="#cb26-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3321 - &lt;span id="cb26-3"&gt;&lt;a href="#cb26-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;syn::&lt;/span&gt;&lt;span class="op"&gt;{&lt;/span&gt;parse_macro_input&lt;span class="op"&gt;,&lt;/span&gt; Block&lt;span class="op"&gt;,&lt;/span&gt; FnArg&lt;span class="op"&gt;,&lt;/span&gt; ItemFn&lt;span class="op"&gt;,&lt;/span&gt; Pat&lt;span class="op"&gt;,&lt;/span&gt; ReturnType&lt;span class="op"&gt;,&lt;/span&gt; Type&lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 3322 - &lt;span id="cb26-4"&gt;&lt;a href="#cb26-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3323 - &lt;span id="cb26-5"&gt;&lt;a href="#cb26-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; extract_type(a&lt;span class="op"&gt;:&lt;/span&gt; FnArg) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;Box&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;Type&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3324 - &lt;span id="cb26-6"&gt;&lt;a href="#cb26-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;match&lt;/span&gt; a &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3325 - &lt;span id="cb26-7"&gt;&lt;a href="#cb26-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;FnArg::&lt;/span&gt;Typed(p) &lt;span class="op"&gt;=&amp;gt;&lt;/span&gt; p&lt;span class="op"&gt;.&lt;/span&gt;ty&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="co"&gt;// notice `ty` instead of `pat`&lt;/span&gt;&lt;/span&gt; 3326 - &lt;span id="cb26-8"&gt;&lt;a href="#cb26-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; _ &lt;span class="op"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pp"&gt;panic!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;Not supported on types with `self`!&amp;quot;&lt;/span&gt;)&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3327 - &lt;span id="cb26-9"&gt;&lt;a href="#cb26-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3328 - &lt;span id="cb26-10"&gt;&lt;a href="#cb26-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3329 - &lt;span id="cb26-11"&gt;&lt;a href="#cb26-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3330 - &lt;span id="cb26-12"&gt;&lt;a href="#cb26-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; extract_arg_types(fn_args&lt;span class="op"&gt;:&lt;/span&gt; Punctuated&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;FnArg&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="pp"&gt;syn::token::&lt;/span&gt;Comma&lt;span class="op"&gt;&amp;gt;&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;Vec&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dt"&gt;Box&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;Type&lt;span class="op"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3331 - &lt;span id="cb26-13"&gt;&lt;a href="#cb26-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; fn_args&lt;span class="op"&gt;.&lt;/span&gt;into_iter()&lt;span class="op"&gt;.&lt;/span&gt;map(extract_type)&lt;span class="op"&gt;.&lt;/span&gt;&lt;span class="pp"&gt;collect::&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dt"&gt;Vec&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;_&lt;span class="op"&gt;&amp;gt;&amp;gt;&lt;/span&gt;()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3332 - &lt;span id="cb26-14"&gt;&lt;a href="#cb26-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3333 - &lt;span id="cb26-15"&gt;&lt;a href="#cb26-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3334 - &lt;p&gt;A good reader would have looked at the docs for output member of the 3335 - &lt;code&gt;syn::Signature&lt;/code&gt; struct. It has the type 3336 - &lt;code&gt;syn::ReturnType&lt;/code&gt;. So there is no extraction to do here 3337 - right? There are actually a couple of things we have to ensure here:&lt;/p&gt; 3338 - &lt;ol type="1"&gt; 3339 - &lt;li&gt;&lt;p&gt;We need to ensure that the function returns! A function that does 3340 - not return is pointless in this case, and I will tell you why, in the &lt;a 3341 - href="#notes"&gt;Notes&lt;/a&gt; section.&lt;/p&gt;&lt;/li&gt; 3342 - &lt;li&gt;&lt;p&gt;A &lt;code&gt;ReturnType&lt;/code&gt; encloses the arrow of the return as 3343 - well, we need to get rid of that. Recall:&lt;/p&gt; 3344 - &lt;div class="sourceCode" id="cb27"&gt;&lt;pre 3345 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb27-1"&gt;&lt;a href="#cb27-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T0 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;/span&gt; 3346 - &lt;span id="cb27-2"&gt;&lt;a href="#cb27-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// and not&lt;/span&gt;&lt;/span&gt; 3347 - &lt;span id="cb27-3"&gt;&lt;a href="#cb27-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T0 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/li&gt; 3348 - &lt;/ol&gt; 3349 - &lt;p&gt;Here is the snippet that handles extraction of the return type (doc: 3350 - &lt;a 3351 - href="https://docs.rs/syn/1.0.19/syn/enum.ReturnType.html"&gt;syn::ReturnType&lt;/a&gt;):&lt;/p&gt; 3352 - &lt;div class="sourceCode" id="cb28"&gt;&lt;pre 3353 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb28-1"&gt;&lt;a href="#cb28-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// src/lib.rs&lt;/span&gt;&lt;/span&gt; 3354 - &lt;span id="cb28-2"&gt;&lt;a href="#cb28-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3355 - &lt;span id="cb28-3"&gt;&lt;a href="#cb28-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; extract_return_type(a&lt;span class="op"&gt;:&lt;/span&gt; ReturnType) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;Box&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;Type&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3356 - &lt;span id="cb28-4"&gt;&lt;a href="#cb28-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;match&lt;/span&gt; a &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3357 - &lt;span id="cb28-5"&gt;&lt;a href="#cb28-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;ReturnType::&lt;/span&gt;Type(_&lt;span class="op"&gt;,&lt;/span&gt; p) &lt;span class="op"&gt;=&amp;gt;&lt;/span&gt; p&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3358 - &lt;span id="cb28-6"&gt;&lt;a href="#cb28-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; _ &lt;span class="op"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="pp"&gt;panic!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;Not supported on functions without return types!&amp;quot;&lt;/span&gt;)&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3359 - &lt;span id="cb28-7"&gt;&lt;a href="#cb28-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3360 - &lt;span id="cb28-8"&gt;&lt;a href="#cb28-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3361 - &lt;p&gt;You might notice that we are making extensive use of the 3362 - &lt;code&gt;panic!&lt;/code&gt; macro. Well, that is because it is a good idea to 3363 - quit on receiving an unsatisfactory &lt;code&gt;TokenStream&lt;/code&gt;.&lt;/p&gt; 3364 - &lt;p&gt;With all our types ready, we can get on with generating type 3365 - aliases:&lt;/p&gt; 3366 - &lt;div class="sourceCode" id="cb29"&gt;&lt;pre 3367 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb29-1"&gt;&lt;a href="#cb29-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// src/lib.rs&lt;/span&gt;&lt;/span&gt; 3368 - &lt;span id="cb29-2"&gt;&lt;a href="#cb29-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3369 - &lt;span id="cb29-3"&gt;&lt;a href="#cb29-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;quote::&lt;/span&gt;&lt;span class="op"&gt;{&lt;/span&gt;quote&lt;span class="op"&gt;,&lt;/span&gt; format_ident&lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 3370 - &lt;span id="cb29-4"&gt;&lt;a href="#cb29-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3371 - &lt;span id="cb29-5"&gt;&lt;a href="#cb29-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; generate_type_aliases(&lt;/span&gt; 3372 - &lt;span id="cb29-6"&gt;&lt;a href="#cb29-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; fn_arg_types&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;[&lt;span class="dt"&gt;Box&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;Type&lt;span class="op"&gt;&amp;gt;&lt;/span&gt;]&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3373 - &lt;span id="cb29-7"&gt;&lt;a href="#cb29-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; fn_return_type&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;Box&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;Type&lt;span class="op"&gt;&amp;gt;,&lt;/span&gt;&lt;/span&gt; 3374 - &lt;span id="cb29-8"&gt;&lt;a href="#cb29-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; fn_name&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;&lt;span class="pp"&gt;syn::&lt;/span&gt;Ident&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3375 - &lt;span id="cb29-9"&gt;&lt;a href="#cb29-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;Vec&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="pp"&gt;proc_macro2::&lt;/span&gt;TokenStream&lt;span class="op"&gt;&amp;gt;&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="co"&gt;// 1&lt;/span&gt;&lt;/span&gt; 3376 - &lt;span id="cb29-10"&gt;&lt;a href="#cb29-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3377 - &lt;span id="cb29-11"&gt;&lt;a href="#cb29-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; type_t0 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;format_ident!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;_{}_T0&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; fn_name)&lt;span class="op"&gt;;&lt;/span&gt; &lt;span class="co"&gt;// 2&lt;/span&gt;&lt;/span&gt; 3378 - &lt;span id="cb29-12"&gt;&lt;a href="#cb29-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; &lt;span class="kw"&gt;mut&lt;/span&gt; type_aliases &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;vec!&lt;/span&gt;[&lt;span class="pp"&gt;quote!&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="kw"&gt;type&lt;/span&gt; #type_t0 &lt;span class="op"&gt;=&lt;/span&gt; #fn_return_type &lt;span class="op"&gt;}&lt;/span&gt;]&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3379 - &lt;span id="cb29-13"&gt;&lt;a href="#cb29-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3380 - &lt;span id="cb29-14"&gt;&lt;a href="#cb29-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="co"&gt;// 3&lt;/span&gt;&lt;/span&gt; 3381 - &lt;span id="cb29-15"&gt;&lt;a href="#cb29-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;for&lt;/span&gt; (i&lt;span class="op"&gt;,&lt;/span&gt; t) &lt;span class="kw"&gt;in&lt;/span&gt; (&lt;span class="dv"&gt;1&lt;/span&gt;&lt;span class="op"&gt;..&lt;/span&gt;)&lt;span class="op"&gt;.&lt;/span&gt;zip(fn_arg_types&lt;span class="op"&gt;.&lt;/span&gt;into_iter()&lt;span class="op"&gt;.&lt;/span&gt;rev()) &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3382 - &lt;span id="cb29-16"&gt;&lt;a href="#cb29-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; p &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;format_ident!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;_{}_{}&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; fn_name&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="pp"&gt;format!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;T{}&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; i &lt;span class="op"&gt;-&lt;/span&gt; &lt;span class="dv"&gt;1&lt;/span&gt;))&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3383 - &lt;span id="cb29-17"&gt;&lt;a href="#cb29-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; n &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;format_ident!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;_{}_{}&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; fn_name&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="pp"&gt;format!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;T{}&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; i))&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3384 - &lt;span id="cb29-18"&gt;&lt;a href="#cb29-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3385 - &lt;span id="cb29-19"&gt;&lt;a href="#cb29-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; type_aliases&lt;span class="op"&gt;.&lt;/span&gt;push(&lt;span class="pp"&gt;quote!&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3386 - &lt;span id="cb29-20"&gt;&lt;a href="#cb29-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;type&lt;/span&gt; #n &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(#t) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; #p&lt;/span&gt; 3387 - &lt;span id="cb29-21"&gt;&lt;a href="#cb29-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3388 - &lt;span id="cb29-22"&gt;&lt;a href="#cb29-22" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3389 - &lt;span id="cb29-23"&gt;&lt;a href="#cb29-23" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3390 - &lt;span id="cb29-24"&gt;&lt;a href="#cb29-24" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; type_aliases&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3391 - &lt;span id="cb29-25"&gt;&lt;a href="#cb29-25" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3392 - &lt;p&gt;&lt;strong&gt;1. The return value&lt;/strong&gt;&lt;br /&gt; 3393 - We are returning a &lt;code&gt;Vec&amp;lt;proc_macro2::TokenStream&amp;gt;&lt;/code&gt;, i. 3394 - e., a list of &lt;code&gt;TokenStream&lt;/code&gt;s, where each item is a type 3395 - alias.&lt;/p&gt; 3396 - &lt;p&gt;&lt;strong&gt;2. Format identifier?&lt;/strong&gt;&lt;br /&gt; 3397 - I’ve got some explanation to do on this line. Clearly, we are trying to 3398 - write the first type alias, and initialize our &lt;code&gt;TokenStream&lt;/code&gt; 3399 - vector with &lt;code&gt;T0&lt;/code&gt;, because it is different from the 3400 - others:&lt;/p&gt; 3401 - &lt;div class="sourceCode" id="cb30"&gt;&lt;pre 3402 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb30-1"&gt;&lt;a href="#cb30-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T0 &lt;span class="op"&gt;=&lt;/span&gt; something&lt;/span&gt; 3403 - &lt;span id="cb30-2"&gt;&lt;a href="#cb30-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// the others are of the form&lt;/span&gt;&lt;/span&gt; 3404 - &lt;span id="cb30-3"&gt;&lt;a href="#cb30-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; Tr &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(something) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; something&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3405 - &lt;p&gt;&lt;code&gt;format_ident!&lt;/code&gt; is similar to &lt;code&gt;format!&lt;/code&gt;. 3406 - Instead of returning a formatted string, it returns a 3407 - &lt;code&gt;syn::Ident&lt;/code&gt;. Therefore, &lt;code&gt;type_t0&lt;/code&gt; is actually an 3408 - identifier for, in the case of our &lt;code&gt;add&lt;/code&gt; function, 3409 - &lt;code&gt;_add_T0&lt;/code&gt;. Why is this formatting important? Namespacing.&lt;/p&gt; 3410 - &lt;p&gt;Picture this, we have two functions, &lt;code&gt;add&lt;/code&gt; and 3411 - &lt;code&gt;subtract&lt;/code&gt;, that we wish to curry with our macro:&lt;/p&gt; 3412 - &lt;div class="sourceCode" id="cb31"&gt;&lt;pre 3413 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb31-1"&gt;&lt;a href="#cb31-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;#[&lt;/span&gt;curry&lt;span class="at"&gt;]&lt;/span&gt;&lt;/span&gt; 3414 - &lt;span id="cb31-2"&gt;&lt;a href="#cb31-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; add(&lt;span class="op"&gt;...&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="op"&gt;...&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3415 - &lt;span id="cb31-3"&gt;&lt;a href="#cb31-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3416 - &lt;span id="cb31-4"&gt;&lt;a href="#cb31-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;#[&lt;/span&gt;curry&lt;span class="at"&gt;]&lt;/span&gt;&lt;/span&gt; 3417 - &lt;span id="cb31-5"&gt;&lt;a href="#cb31-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; sub(&lt;span class="op"&gt;...&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="op"&gt;...&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3418 - &lt;p&gt;Here is the same but with macros expanded:&lt;/p&gt; 3419 - &lt;div class="sourceCode" id="cb32"&gt;&lt;pre 3420 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb32-1"&gt;&lt;a href="#cb32-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T0 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3421 - &lt;span id="cb32-2"&gt;&lt;a href="#cb32-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T1 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; T0&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3422 - &lt;span id="cb32-3"&gt;&lt;a href="#cb32-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; add( &lt;span class="op"&gt;...&lt;/span&gt; ) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; T1 &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="op"&gt;...&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3423 - &lt;span id="cb32-4"&gt;&lt;a href="#cb32-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3424 - &lt;span id="cb32-5"&gt;&lt;a href="#cb32-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T0 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3425 - &lt;span id="cb32-6"&gt;&lt;a href="#cb32-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; T1 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; T0&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3426 - &lt;span id="cb32-7"&gt;&lt;a href="#cb32-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; sub( &lt;span class="op"&gt;...&lt;/span&gt; ) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; T1 &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="op"&gt;...&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3427 - &lt;p&gt;We end up with two definitions of &lt;code&gt;T0&lt;/code&gt;! Now, if we do the 3428 - little &lt;code&gt;format_ident!&lt;/code&gt; dance we did up there:&lt;/p&gt; 3429 - &lt;div class="sourceCode" id="cb33"&gt;&lt;pre 3430 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb33-1"&gt;&lt;a href="#cb33-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; _add_T0 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3431 - &lt;span id="cb33-2"&gt;&lt;a href="#cb33-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; _add_T1 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; _add_T0&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3432 - &lt;span id="cb33-3"&gt;&lt;a href="#cb33-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; add( &lt;span class="op"&gt;...&lt;/span&gt; ) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; _add_T1 &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="op"&gt;...&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3433 - &lt;span id="cb33-4"&gt;&lt;a href="#cb33-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3434 - &lt;span id="cb33-5"&gt;&lt;a href="#cb33-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; _sub_T0 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3435 - &lt;span id="cb33-6"&gt;&lt;a href="#cb33-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; _sub_T1 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; _sub_T0&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3436 - &lt;span id="cb33-7"&gt;&lt;a href="#cb33-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; sub( &lt;span class="op"&gt;...&lt;/span&gt; ) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; _sub_T1 &lt;span class="op"&gt;{&lt;/span&gt; &lt;span class="op"&gt;...&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3437 - &lt;p&gt;Voilà! The type aliases don’t tread on each other. Remember to import 3438 - &lt;code&gt;format_ident&lt;/code&gt; from the &lt;code&gt;quote&lt;/code&gt; crate.&lt;/p&gt; 3439 - &lt;p&gt;&lt;strong&gt;3. The TokenStream Vector&lt;/strong&gt;&lt;/p&gt; 3440 - &lt;p&gt;We iterate over our types in reverse order (&lt;code&gt;T0&lt;/code&gt; is the 3441 - last return, &lt;code&gt;T1&lt;/code&gt; is the second last, so on), assign a number 3442 - to each iteration with &lt;code&gt;zip&lt;/code&gt;, generate type names with 3443 - &lt;code&gt;format_ident&lt;/code&gt;, push a &lt;code&gt;TokenStream&lt;/code&gt; with the help 3444 - of &lt;code&gt;quote&lt;/code&gt; and variable interpolation.&lt;/p&gt; 3445 - &lt;p&gt;If you are wondering why we used &lt;code&gt;(1..).zip()&lt;/code&gt; instead of 3446 - &lt;code&gt;.enumerate()&lt;/code&gt;, it’s because we wanted to start counting from 3447 - 1 instead of 0 (we are already done with &lt;code&gt;T0&lt;/code&gt;!).&lt;/p&gt; 3448 - &lt;h4 id="getting-it-together"&gt;Getting it together&lt;/h4&gt; 3449 - &lt;p&gt;I promised we’d have a fully working macro by the end of last 3450 - section. I lied, we have to tie everything together in our 3451 - &lt;code&gt;generate_curry&lt;/code&gt; function:&lt;/p&gt; 3452 - &lt;div class="sourceCode" id="cb34"&gt;&lt;pre 3453 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb34-1"&gt;&lt;a href="#cb34-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// src/lib.rs&lt;/span&gt;&lt;/span&gt; 3454 - &lt;span id="cb34-2"&gt;&lt;a href="#cb34-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3455 - &lt;span id="cb34-3"&gt;&lt;a href="#cb34-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;fn&lt;/span&gt; generate_curry(parsed&lt;span class="op"&gt;:&lt;/span&gt; ItemFn) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="pp"&gt;proc_macro2::&lt;/span&gt;TokenStream &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3456 - &lt;span id="cb34-4"&gt;&lt;a href="#cb34-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_body &lt;span class="op"&gt;=&lt;/span&gt; parsed&lt;span class="op"&gt;.&lt;/span&gt;block&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3457 - &lt;span id="cb34-5"&gt;&lt;a href="#cb34-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; sig &lt;span class="op"&gt;=&lt;/span&gt; parsed&lt;span class="op"&gt;.&lt;/span&gt;sig&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3458 - &lt;span id="cb34-6"&gt;&lt;a href="#cb34-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; vis &lt;span class="op"&gt;=&lt;/span&gt; parsed&lt;span class="op"&gt;.&lt;/span&gt;vis&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3459 - &lt;span id="cb34-7"&gt;&lt;a href="#cb34-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_name &lt;span class="op"&gt;=&lt;/span&gt; sig&lt;span class="op"&gt;.&lt;/span&gt;ident&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3460 - &lt;span id="cb34-8"&gt;&lt;a href="#cb34-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_args &lt;span class="op"&gt;=&lt;/span&gt; sig&lt;span class="op"&gt;.&lt;/span&gt;inputs&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3461 - &lt;span id="cb34-9"&gt;&lt;a href="#cb34-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; fn_return_type &lt;span class="op"&gt;=&lt;/span&gt; sig&lt;span class="op"&gt;.&lt;/span&gt;output&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3462 - &lt;span id="cb34-10"&gt;&lt;a href="#cb34-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3463 - &lt;span id="cb34-11"&gt;&lt;a href="#cb34-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; arg_idents &lt;span class="op"&gt;=&lt;/span&gt; extract_arg_idents(fn_args&lt;span class="op"&gt;.&lt;/span&gt;clone())&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3464 - &lt;span id="cb34-12"&gt;&lt;a href="#cb34-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; first_ident &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;arg_idents&lt;span class="op"&gt;.&lt;/span&gt;first()&lt;span class="op"&gt;.&lt;/span&gt;unwrap()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3465 - &lt;span id="cb34-13"&gt;&lt;a href="#cb34-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; curried_body &lt;span class="op"&gt;=&lt;/span&gt; generate_body(&lt;span class="op"&gt;&amp;amp;&lt;/span&gt;arg_idents[&lt;span class="dv"&gt;1&lt;/span&gt;&lt;span class="op"&gt;..&lt;/span&gt;]&lt;span class="op"&gt;,&lt;/span&gt; fn_body&lt;span class="op"&gt;.&lt;/span&gt;clone())&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3466 - &lt;span id="cb34-14"&gt;&lt;a href="#cb34-14" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3467 - &lt;span id="cb34-15"&gt;&lt;a href="#cb34-15" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="kw"&gt;let&lt;/span&gt; arg_types &lt;span class="op"&gt;=&lt;/span&gt; extract_arg_types(fn_args&lt;span class="op"&gt;.&lt;/span&gt;clone())&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3468 - &lt;span id="cb34-16"&gt;&lt;a href="#cb34-16" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="kw"&gt;let&lt;/span&gt; first_type &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;arg_types&lt;span class="op"&gt;.&lt;/span&gt;first()&lt;span class="op"&gt;.&lt;/span&gt;unwrap()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3469 - &lt;span id="cb34-17"&gt;&lt;a href="#cb34-17" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="kw"&gt;let&lt;/span&gt; type_aliases &lt;span class="op"&gt;=&lt;/span&gt; generate_type_aliases(&lt;/span&gt; 3470 - &lt;span id="cb34-18"&gt;&lt;a href="#cb34-18" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;arg_types[&lt;span class="dv"&gt;1&lt;/span&gt;&lt;span class="op"&gt;..&lt;/span&gt;]&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3471 - &lt;span id="cb34-19"&gt;&lt;a href="#cb34-19" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; extract_return_type(fn_return_type)&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3472 - &lt;span id="cb34-20"&gt;&lt;a href="#cb34-20" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;fn_name&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3473 - &lt;span id="cb34-21"&gt;&lt;a href="#cb34-21" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; )&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3474 - &lt;span id="cb34-22"&gt;&lt;a href="#cb34-22" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3475 - &lt;span id="cb34-23"&gt;&lt;a href="#cb34-23" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="kw"&gt;let&lt;/span&gt; return_type &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="pp"&gt;format_ident!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;_{}_{}&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="op"&gt;&amp;amp;&lt;/span&gt;fn_name&lt;span class="op"&gt;,&lt;/span&gt; &lt;span class="pp"&gt;format!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;T{}&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; type_aliases&lt;span class="op"&gt;.&lt;/span&gt;len() &lt;span class="op"&gt;-&lt;/span&gt; &lt;span class="dv"&gt;1&lt;/span&gt;))&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3476 - &lt;span id="cb34-24"&gt;&lt;a href="#cb34-24" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3477 - &lt;span id="cb34-25"&gt;&lt;a href="#cb34-25" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="cf"&gt;return&lt;/span&gt; &lt;span class="pp"&gt;quote!&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3478 - &lt;span id="cb34-26"&gt;&lt;a href="#cb34-26" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; #(#type_aliases)&lt;span class="op"&gt;;*&lt;/span&gt; &lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3479 - &lt;span id="cb34-27"&gt;&lt;a href="#cb34-27" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; #vis &lt;span class="kw"&gt;fn&lt;/span&gt; #fn_name (#first_ident&lt;span class="op"&gt;:&lt;/span&gt; #first_type) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; #return_type &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3480 - &lt;span id="cb34-28"&gt;&lt;a href="#cb34-28" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; #curried_body &lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3481 - &lt;span id="cb34-29"&gt;&lt;a href="#cb34-29" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3482 - &lt;span id="cb34-30"&gt;&lt;a href="#cb34-30" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;+&lt;/span&gt; &lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 3483 - &lt;span id="cb34-31"&gt;&lt;a href="#cb34-31" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3484 - &lt;p&gt;Most of the additions are self explanatory, I’ll go through the 3485 - return statement with you. We are returning a 3486 - &lt;code&gt;quote!{ ... }&lt;/code&gt;, so a &lt;code&gt;proc_macro2::TokenStream&lt;/code&gt;. 3487 - We are iterating through the &lt;code&gt;type_aliases&lt;/code&gt; variable, which 3488 - you might recall, is a &lt;code&gt;Vec&amp;lt;TokenStream&amp;gt;&lt;/code&gt;. You might 3489 - notice the sneaky semicolon before the &lt;code&gt;*&lt;/code&gt;. This basically 3490 - tells &lt;code&gt;quote&lt;/code&gt;, to insert an item, then a semicolon, and then 3491 - the next one, another semicolon, and so on. The semicolon is a 3492 - separator. We need to manually insert another semicolon at the end of it 3493 - all, &lt;code&gt;quote&lt;/code&gt; doesn’t insert a separator at the end of the 3494 - iteration.&lt;/p&gt; 3495 - &lt;p&gt;We retain the visibility and name of our original function. Our 3496 - curried function takes as args, just the first argument of our original 3497 - function. The return type of our curried function is actually, the last 3498 - type alias we create. If you think back to our manually curried 3499 - &lt;code&gt;add&lt;/code&gt; function, we returned &lt;code&gt;T2&lt;/code&gt;, which was in 3500 - fact, the last type alias we created.&lt;/p&gt; 3501 - &lt;p&gt;I am sure, at this point, you are itching to test this out, but 3502 - before that, let me introduce you to some good methods of debugging 3503 - proc-macro code.&lt;/p&gt; 3504 - &lt;h3 id="debugging-and-testing"&gt;Debugging and Testing&lt;/h3&gt; 3505 - &lt;p&gt;Install &lt;code&gt;cargo-expand&lt;/code&gt; via:&lt;/p&gt; 3506 - &lt;pre&gt;&lt;code&gt;cargo install cargo-expand&lt;/code&gt;&lt;/pre&gt; 3507 - &lt;p&gt;&lt;code&gt;cargo-expand&lt;/code&gt; is a neat little tool that expands your 3508 - macro in places where it is used, and lets you view the generated code! 3509 - For example:&lt;/p&gt; 3510 - &lt;pre class="shell"&gt;&lt;code&gt;# create a bin package hello 3511 - $ cargo new hello 3512 - 3513 - # view the expansion of the println! macro 3514 - $ cargo expand 3515 - 3516 - #![feature(prelude_import)] 3517 - #[prelude_import] 3518 - use std::prelude::v1::*; 3519 - #[macro_use] 3520 - extern crate std; 3521 - fn main() { 3522 - { 3523 - ::std::io::_print(::core::fmt::Arguments::new_v1( 3524 - &amp;amp;[&amp;quot;Hello, world!\n&amp;quot;], 3525 - &amp;amp;match () { 3526 - () =&amp;gt; [], 3527 - }, 3528 - )); 3529 - }; 3530 - }&lt;/code&gt;&lt;/pre&gt; 3531 - &lt;p&gt;Writing proc-macros without &lt;code&gt;cargo-expand&lt;/code&gt; is tantamount 3532 - to driving a vehicle without rear view mirrors! Keep an eye on what is 3533 - going on behind your back.&lt;/p&gt; 3534 - &lt;p&gt;Now, your macro won’t always compile, you might just recieve the bee 3535 - movie script as an error. &lt;code&gt;cargo-expand&lt;/code&gt; will not work in 3536 - such cases. I would suggest printing out your variables to inspect them. 3537 - &lt;code&gt;TokenStream&lt;/code&gt; implements &lt;code&gt;Display&lt;/code&gt; as well as 3538 - &lt;code&gt;Debug&lt;/code&gt;. We don’t always have to be respectable programmers. 3539 - Just print it.&lt;/p&gt; 3540 - &lt;p&gt;Enough of that, lets get testing:&lt;/p&gt; 3541 - &lt;div class="sourceCode" id="cb37"&gt;&lt;pre 3542 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb37-1"&gt;&lt;a href="#cb37-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// tests/smoke.rs&lt;/span&gt;&lt;/span&gt; 3543 - &lt;span id="cb37-2"&gt;&lt;a href="#cb37-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3544 - &lt;span id="cb37-3"&gt;&lt;a href="#cb37-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;#![&lt;/span&gt;feature&lt;span class="at"&gt;(&lt;/span&gt;type_alias_impl_trait&lt;span class="at"&gt;)]&lt;/span&gt;&lt;/span&gt; 3545 - &lt;span id="cb37-4"&gt;&lt;a href="#cb37-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3546 - &lt;span id="cb37-5"&gt;&lt;a href="#cb37-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;#[&lt;/span&gt;&lt;span class="pp"&gt;crate_name::&lt;/span&gt;curry&lt;span class="at"&gt;]&lt;/span&gt;&lt;/span&gt; 3547 - &lt;span id="cb37-6"&gt;&lt;a href="#cb37-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; add(x&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; y&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; z&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3548 - &lt;span id="cb37-7"&gt;&lt;a href="#cb37-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; x &lt;span class="op"&gt;+&lt;/span&gt; y &lt;span class="op"&gt;+&lt;/span&gt; z&lt;/span&gt; 3549 - &lt;span id="cb37-8"&gt;&lt;a href="#cb37-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3550 - &lt;span id="cb37-9"&gt;&lt;a href="#cb37-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3551 - &lt;span id="cb37-10"&gt;&lt;a href="#cb37-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;#[&lt;/span&gt;test&lt;span class="at"&gt;]&lt;/span&gt;&lt;/span&gt; 3552 - &lt;span id="cb37-11"&gt;&lt;a href="#cb37-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; works() &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3553 - &lt;span id="cb37-12"&gt;&lt;a href="#cb37-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;assert_eq!&lt;/span&gt;(&lt;span class="dv"&gt;15&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; add(&lt;span class="dv"&gt;4&lt;/span&gt;)(&lt;span class="dv"&gt;5&lt;/span&gt;)(&lt;span class="dv"&gt;6&lt;/span&gt;))&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3554 - &lt;span id="cb37-13"&gt;&lt;a href="#cb37-13" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3555 - &lt;p&gt;Run &lt;code&gt;cargo +nightly test&lt;/code&gt;. You should see a pleasing 3556 - message:&lt;/p&gt; 3557 - &lt;pre&gt;&lt;code&gt;running 1 test 3558 - test tests::works ... ok&lt;/code&gt;&lt;/pre&gt; 3559 - &lt;p&gt;Take a look at the expansion for our curry macro, via 3560 - &lt;code&gt;cargo +nightly expand --tests smoke&lt;/code&gt;:&lt;/p&gt; 3561 - &lt;div class="sourceCode" id="cb39"&gt;&lt;pre 3562 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb39-1"&gt;&lt;a href="#cb39-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; _add_T0 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3563 - &lt;span id="cb39-2"&gt;&lt;a href="#cb39-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; _add_T1 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; _add_T0&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3564 - &lt;span id="cb39-3"&gt;&lt;a href="#cb39-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;type&lt;/span&gt; _add_T2 &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="kw"&gt;impl&lt;/span&gt; &lt;span class="bu"&gt;Fn&lt;/span&gt;(&lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; _add_T1&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3565 - &lt;span id="cb39-4"&gt;&lt;a href="#cb39-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; add(x&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; _add_T2 &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3566 - &lt;span id="cb39-5"&gt;&lt;a href="#cb39-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; (&lt;span class="kw"&gt;move&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;y&lt;span class="op"&gt;|&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3567 - &lt;span id="cb39-6"&gt;&lt;a href="#cb39-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;move&lt;/span&gt; &lt;span class="op"&gt;|&lt;/span&gt;z&lt;span class="op"&gt;|&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3568 - &lt;span id="cb39-7"&gt;&lt;a href="#cb39-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; x &lt;span class="op"&gt;+&lt;/span&gt; y &lt;span class="op"&gt;+&lt;/span&gt; z&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3569 - &lt;span id="cb39-8"&gt;&lt;a href="#cb39-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3570 - &lt;span id="cb39-9"&gt;&lt;a href="#cb39-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3571 - &lt;span id="cb39-10"&gt;&lt;a href="#cb39-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3572 - &lt;span id="cb39-11"&gt;&lt;a href="#cb39-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3573 - &lt;span id="cb39-12"&gt;&lt;a href="#cb39-12" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;// a bunch of other stuff generated by #[test] and assert_eq!&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3574 - &lt;p&gt;A sight for sore eyes.&lt;/p&gt; 3575 - &lt;p&gt;Here is a more complex example that generates ten multiples of the 3576 - first ten natural numbers:&lt;/p&gt; 3577 - &lt;div class="sourceCode" id="cb40"&gt;&lt;pre 3578 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb40-1"&gt;&lt;a href="#cb40-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="at"&gt;#[&lt;/span&gt;curry&lt;span class="at"&gt;]&lt;/span&gt;&lt;/span&gt; 3579 - &lt;span id="cb40-2"&gt;&lt;a href="#cb40-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; product(x&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; y&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt;) &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;u32&lt;/span&gt; &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3580 - &lt;span id="cb40-3"&gt;&lt;a href="#cb40-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; x &lt;span class="op"&gt;*&lt;/span&gt; y&lt;/span&gt; 3581 - &lt;span id="cb40-4"&gt;&lt;a href="#cb40-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3582 - &lt;span id="cb40-5"&gt;&lt;a href="#cb40-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3583 - &lt;span id="cb40-6"&gt;&lt;a href="#cb40-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;fn&lt;/span&gt; multiples() &lt;span class="op"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="dt"&gt;Vec&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dt"&gt;Vec&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;&lt;span class="dt"&gt;u32&lt;/span&gt;&lt;span class="op"&gt;&amp;gt;&amp;gt;{&lt;/span&gt;&lt;/span&gt; 3584 - &lt;span id="cb40-7"&gt;&lt;a href="#cb40-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; v &lt;span class="op"&gt;=&lt;/span&gt; (&lt;span class="dv"&gt;1&lt;/span&gt;&lt;span class="op"&gt;..=&lt;/span&gt;&lt;span class="dv"&gt;10&lt;/span&gt;)&lt;span class="op"&gt;.&lt;/span&gt;map(product)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3585 - &lt;span id="cb40-8"&gt;&lt;a href="#cb40-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="cf"&gt;return&lt;/span&gt; (&lt;span class="dv"&gt;1&lt;/span&gt;&lt;span class="op"&gt;..=&lt;/span&gt;&lt;span class="dv"&gt;10&lt;/span&gt;)&lt;/span&gt; 3586 - &lt;span id="cb40-9"&gt;&lt;a href="#cb40-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;.&lt;/span&gt;map(&lt;span class="op"&gt;|&lt;/span&gt;x&lt;span class="op"&gt;|&lt;/span&gt; v&lt;span class="op"&gt;.&lt;/span&gt;clone()&lt;span class="op"&gt;.&lt;/span&gt;map(&lt;span class="op"&gt;|&lt;/span&gt;f&lt;span class="op"&gt;|&lt;/span&gt; f(x))&lt;span class="op"&gt;.&lt;/span&gt;collect())&lt;/span&gt; 3587 - &lt;span id="cb40-10"&gt;&lt;a href="#cb40-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;.&lt;/span&gt;collect()&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3588 - &lt;span id="cb40-11"&gt;&lt;a href="#cb40-11" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3589 - &lt;h3 id="notes"&gt;Notes&lt;/h3&gt; 3590 - &lt;p&gt;I didn’t quite explain why we use &lt;code&gt;move |arg|&lt;/code&gt; in our 3591 - closure. This is because we want to take ownership of the variable 3592 - supplied to us. Take a look at this example:&lt;/p&gt; 3593 - &lt;div class="sourceCode" id="cb41"&gt;&lt;pre 3594 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb41-1"&gt;&lt;a href="#cb41-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; v &lt;span class="op"&gt;=&lt;/span&gt; add(&lt;span class="dv"&gt;5&lt;/span&gt;)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3595 - &lt;span id="cb41-2"&gt;&lt;a href="#cb41-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="kw"&gt;let&lt;/span&gt; g&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3596 - &lt;span id="cb41-3"&gt;&lt;a href="#cb41-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3597 - &lt;span id="cb41-4"&gt;&lt;a href="#cb41-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;let&lt;/span&gt; x &lt;span class="op"&gt;=&lt;/span&gt; &lt;span class="dv"&gt;5&lt;/span&gt;&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3598 - &lt;span id="cb41-5"&gt;&lt;a href="#cb41-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; g &lt;span class="op"&gt;=&lt;/span&gt; v(x)&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3599 - &lt;span id="cb41-6"&gt;&lt;a href="#cb41-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt; 3600 - &lt;span id="cb41-7"&gt;&lt;a href="#cb41-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="pp"&gt;println!&lt;/span&gt;(&lt;span class="st"&gt;&amp;quot;{}&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;,&lt;/span&gt; g(&lt;span class="dv"&gt;2&lt;/span&gt;))&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3601 - &lt;p&gt;Variable &lt;code&gt;x&lt;/code&gt; goes out of scope before &lt;code&gt;g&lt;/code&gt; can 3602 - return a concrete value. If we take ownership of &lt;code&gt;x&lt;/code&gt; by 3603 - &lt;code&gt;move&lt;/code&gt;ing it into our closure, we can expect this to work 3604 - reliably. In fact, rustc understands this, and forces you to use 3605 - &lt;code&gt;move&lt;/code&gt;.&lt;/p&gt; 3606 - &lt;p&gt;This usage of &lt;code&gt;move&lt;/code&gt; is exactly why &lt;strong&gt;a curried 3607 - function without a return is useless&lt;/strong&gt;. Every variable we pass to 3608 - our curried function gets moved into its local scope. Playing with these 3609 - variables cannot cause a change outside this scope. Returning is our 3610 - only method of interaction with anything beyond this function.&lt;/p&gt; 3611 - &lt;h3 id="conclusion"&gt;Conclusion&lt;/h3&gt; 3612 - &lt;p&gt;Currying may not seem to be all that useful. Curried functions are 3613 - unwieldy in Rust because the standard library is not built around 3614 - currying. If you enjoy the possibilities posed by currying, consider 3615 - taking a look at Haskell or Scheme.&lt;/p&gt; 3616 - &lt;p&gt;My original intention with &lt;a href="https://peppe.rs"&gt;peppe.rs&lt;/a&gt; 3617 - was to post condensed articles, a micro blog, but this one turned out 3618 - extra long.&lt;/p&gt; 3619 - &lt;p&gt;Perhaps I should call it a ‘macro’ blog :)&lt;/p&gt; 3620 - &lt;section id="footnotes" class="footnotes footnotes-end-of-document" 3621 - role="doc-endnotes"&gt; 3622 - &lt;hr /&gt; 3623 - &lt;ol&gt; 3624 - &lt;li id="fn1"&gt;&lt;p&gt;&lt;a 3625 - href="https://doc.rust-lang.org/book/ch13-01-closures.html"&gt;https://doc.rust-lang.org/book/ch13-01-closures.html&lt;/a&gt;&lt;a 3626 - href="#fnref1" class="footnote-back" role="doc-backlink"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt; 3627 - &lt;li id="fn2"&gt;&lt;p&gt;&lt;a href="https://caniuse.rs"&gt;caniuse.rs&lt;/a&gt; contains an 3628 - indexed list of features and their status.&lt;a href="#fnref2" 3629 - class="footnote-back" role="doc-backlink"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt; 3630 - &lt;/ol&gt; 3631 - &lt;/section&gt;</description> 3632 - <link>https://oppi.li/posts/auto-currying_rust_functions/</link> 3633 - <pubDate>Fri, 08 May 2020 18:30:00 +0000</pubDate> 3634 - <guid>https://oppi.li/posts/auto-currying_rust_functions/</guid> 3635 - </item> 3636 - <item> 3637 - <title>Pixel Art In GIMP</title> 3638 - <description>&lt;p&gt;I’ve always been an admirer of pixel art, because of it’s simplicity 3639 - and it’s resemblance to bitmap font design. Recently, I decided to take 3640 - the dive and make some art of my own.&lt;/p&gt; 3641 - &lt;p&gt;I used GIMP because I am fairly familiar with it. Aseprite seems to 3642 - be the editor of choice for animated pixel art though.&lt;/p&gt; 3643 - &lt;h3 id="setting-up-the-canvas"&gt;Setting up the canvas&lt;/h3&gt; 3644 - &lt;p&gt;Picking a canvas size is daunting. Too small, and you won’t be able 3645 - to fit in enough detail to make a legible piece. Too big and you’ve got 3646 - too many pixels to work with!&lt;/p&gt; 3647 - &lt;p&gt;I would suggest starting out with anywhere between 100x100 and 3648 - 200x200. &lt;a href="https://u.peppe.rs/u9.png"&gt;Here’s&lt;/a&gt; a sample 3649 - configuration.&lt;/p&gt; 3650 - &lt;p&gt;Sometimes I use a 10x10 grid, &lt;code&gt;View &amp;gt; Show Grid&lt;/code&gt; and 3651 - &lt;code&gt;Edit &amp;gt; Preferences &amp;gt; Default Grid &amp;gt; Spacing&lt;/code&gt;, but 3652 - that can get jarring, so I throw down a couple of guides, drag right or 3653 - down from the left or top gutters for vertical and horizontal guides 3654 - respectively.&lt;/p&gt; 3655 - &lt;h3 id="choosing-a-brush"&gt;Choosing a Brush&lt;/h3&gt; 3656 - &lt;p&gt;The most important part of our setup is the brush. Use the Pencil 3657 - Tool (&lt;code&gt;n&lt;/code&gt; on the keyboard) for hard edge drawings. Here’s a 3658 - small comparison if you don’t know the difference between a hard edge 3659 - and a soft edge:&lt;/p&gt; 3660 - &lt;figure&gt; 3661 - &lt;img src="https://u.peppe.rs/kz.png" alt="Hard edge vs Soft Edge" /&gt; 3662 - &lt;figcaption aria-hidden="true"&gt;Hard edge vs Soft Edge&lt;/figcaption&gt; 3663 - &lt;/figure&gt; 3664 - &lt;p&gt;I turn the size down all the way to 1 (&lt;code&gt;[&lt;/code&gt; on the 3665 - keyboard). Set &lt;code&gt;Dynamics&lt;/code&gt; off. &lt;a 3666 - href="https://u.peppe.rs/Fs.png"&gt;Here’s&lt;/a&gt; a sample brush 3667 - configuration.&lt;/p&gt; 3668 - &lt;h3 id="laying-down-the-pixels"&gt;Laying down the pixels!&lt;/h3&gt; 3669 - &lt;p&gt;With the boring stuff out of the way, we can start with our piece. I 3670 - usually follow a three step process:&lt;/p&gt; 3671 - &lt;ul&gt; 3672 - &lt;li&gt;draw a rough outline&lt;/li&gt; 3673 - &lt;li&gt;fill in the shadows&lt;/li&gt; 3674 - &lt;li&gt;add highlights&lt;/li&gt; 3675 - &lt;/ul&gt; 3676 - &lt;p&gt;But this process is better explained with an example: an onigiri. Let 3677 - us start off with a 100x100 canvas.&lt;/p&gt; 3678 - &lt;h4 id="drawing-the-outline"&gt;Drawing the outline&lt;/h4&gt; 3679 - &lt;p&gt;For the most part, our figure will be symmetric. If you are on GIMP 3680 - 2.10+, you can take advantage of the Symmetry Painting feature. Go ahead 3681 - and enable vertical symmetry, 3682 - &lt;code&gt;Window &amp;gt; Dockable Dialogs &amp;gt; Symmetry Painting&lt;/code&gt; and 3683 - &lt;code&gt;Symmetry Painting &amp;gt; Symmetry &amp;gt; Mirror &amp;gt; Vertical&lt;/code&gt;.&lt;/p&gt; 3684 - &lt;p&gt;If you are running an older version of GIMP, draw in the left side, 3685 - duplicate the layer, flip it horizontally, and merge it with the 3686 - original.&lt;/p&gt; 3687 - &lt;p&gt;Your outline might look something like this:&lt;/p&gt; 3688 - &lt;p&gt;&lt;img src="https://u.peppe.rs/mn.png" /&gt;&lt;/p&gt; 3689 - &lt;p&gt;Go ahead and fill it in with the fill tool (&lt;code&gt;Shift + b&lt;/code&gt; on 3690 - the keyboard), add in some seaweed as well, preferably on a different 3691 - layer. You can toggle symmetry on and off to save yourself some 3692 - time.&lt;/p&gt; 3693 - &lt;p&gt;&lt;img src="https://u.peppe.rs/xu.png" /&gt;&lt;/p&gt; 3694 - &lt;h4 id="shadows"&gt;Shadows&lt;/h4&gt; 3695 - &lt;p&gt;For now, let us focus on the shadows on the object itself, we’ll come 3696 - back to the shadows cast by the object on the surface later.&lt;/p&gt; 3697 - &lt;p&gt;Shadows on any surface always follow the shape of the surface. A 3698 - spherical onigiri would have a circular shadow:&lt;/p&gt; 3699 - &lt;p&gt;&lt;img src="https://u.peppe.rs/FU.png" /&gt;&lt;/p&gt; 3700 - &lt;p&gt;A couple of noticeable changes:&lt;/p&gt; 3701 - &lt;p&gt;&lt;strong&gt;Layers&lt;/strong&gt;: The layer containing the seaweed has been 3702 - hidden.&lt;br /&gt; 3703 - &lt;strong&gt;Color&lt;/strong&gt;: The color of the shadow is just a slightly 3704 - lighter version of the original object (reduce the Value on the HSV 3705 - scale).&lt;br /&gt; 3706 - &lt;strong&gt;Area&lt;/strong&gt;: The shadow does not go all the way (notice the 3707 - bottom edges).&lt;/p&gt; 3708 - &lt;p&gt;The shadow does not go all the way because we will be filling in that 3709 - area with another, darker shadow! An image might explain better:&lt;/p&gt; 3710 - &lt;p&gt;&lt;img src="https://u.peppe.rs/Br.png" /&gt;&lt;/p&gt; 3711 - &lt;p&gt;To emulate soft lights, reduce the value by 2 to 3 points every 3712 - iteration. Notice how area &lt;code&gt;1&lt;/code&gt; is much larger than area 3713 - &lt;code&gt;4&lt;/code&gt;. This is because an onigiri resembles a bottom heavy 3714 - oblate spheroid, a sphere that is slightly fatter around the lower 3715 - bottom, and areas &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;2&lt;/code&gt; catch more light 3716 - than areas &lt;code&gt;3&lt;/code&gt; and &lt;code&gt;4&lt;/code&gt;.&lt;/p&gt; 3717 - &lt;p&gt;Do the same with the seaweed. The seaweed, being a smaller, flatter 3718 - object, doesn’t cast much of a shadow, so stop with 1 or 2 iterations of 3719 - the gradient:&lt;/p&gt; 3720 - &lt;p&gt;&lt;img src="https://u.peppe.rs/T3.png" /&gt;&lt;/p&gt; 3721 - &lt;p&gt;We’re getting there!&lt;/p&gt; 3722 - &lt;h4 id="highlights"&gt;Highlights&lt;/h4&gt; 3723 - &lt;p&gt;This step handles the details on the strongly illuminated portions of 3724 - the object. Seaweed is a bit glossy, lighten the edges to make it seem 3725 - shiny. The rice is not as shiny, but it does form an uneven surface. Add 3726 - in some shadows to promote the idea of rice grains. Here is the finished 3727 - result:&lt;/p&gt; 3728 - &lt;p&gt;&lt;img src="https://u.peppe.rs/VE.png" /&gt;&lt;/p&gt; 3729 - &lt;h3 id="finishing-touches"&gt;Finishing Touches&lt;/h3&gt; 3730 - &lt;p&gt;Some color correction and &lt;code&gt;a e s t h e t i c&lt;/code&gt; Japanese 3731 - text later, our piece is complete!&lt;/p&gt; 3732 - &lt;p&gt;&lt;img src="https://u.peppe.rs/cn.png" /&gt;&lt;/p&gt; 3733 - &lt;p&gt;Hold on, why is it so tiny? Well, that’s because our canvas was 3734 - 100x100, head over to &lt;code&gt;Image &amp;gt; Scale Image&lt;/code&gt;, set 3735 - &lt;code&gt;Quality &amp;gt; Interpolation&lt;/code&gt; to &lt;code&gt;None&lt;/code&gt; and scale 3736 - it up to 700x700, et voilà!&lt;/p&gt; 3737 - &lt;p&gt;&lt;img src="https://u.peppe.rs/CH.png" /&gt;&lt;/p&gt;</description> 3738 - <link>https://oppi.li/posts/pixel_art_in_GIMP/</link> 3739 - <pubDate>Wed, 08 Apr 2020 18:30:00 +0000</pubDate> 3740 - <guid>https://oppi.li/posts/pixel_art_in_GIMP/</guid> 3741 - </item> 3742 - <item> 3743 - <title>Rapid Refactoring With Vim</title> 3744 - <description>&lt;p&gt;Last weekend, I was tasked with refactoring the 96 unit tests on &lt;a 3745 - href="https://github.com/ruma/ruma-events/pull/70"&gt;ruma-events&lt;/a&gt; to 3746 - use strictly typed json objects using &lt;code&gt;serde_json::json!&lt;/code&gt; 3747 - instead of raw strings. It was rather painless thanks to vim :)&lt;/p&gt; 3748 - &lt;p&gt;Here’s a small sample of what had to be done (note the lines prefixed 3749 - with the arrow):&lt;/p&gt; 3750 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre 3751 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;→ &lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;serde_json::&lt;/span&gt;&lt;span class="op"&gt;{&lt;/span&gt;from_str&lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 3752 - &lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 3753 - &lt;span id="cb1-3"&gt;&lt;a href="#cb1-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="at"&gt;#[&lt;/span&gt;test&lt;span class="at"&gt;]&lt;/span&gt;&lt;/span&gt; 3754 - &lt;span id="cb1-4"&gt;&lt;a href="#cb1-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;fn&lt;/span&gt; deserialize() &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3755 - &lt;span id="cb1-5"&gt;&lt;a href="#cb1-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;assert_eq!&lt;/span&gt;(&lt;/span&gt; 3756 - &lt;span id="cb1-6"&gt;&lt;a href="#cb1-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;→ &lt;span class="pp"&gt;from_str::&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;Action&lt;span class="op"&gt;&amp;gt;&lt;/span&gt;(&lt;span class="st"&gt;r#&amp;quot;{&amp;quot;set_tweak&amp;quot;: &amp;quot;highlight&amp;quot;}&amp;quot;#&lt;/span&gt;)&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3757 - &lt;span id="cb1-7"&gt;&lt;a href="#cb1-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;Action::&lt;/span&gt;SetTweak(&lt;span class="pp"&gt;Tweak::&lt;/span&gt;Highlight &lt;span class="op"&gt;{&lt;/span&gt; value&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="cn"&gt;true&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;)&lt;/span&gt; 3758 - &lt;span id="cb1-8"&gt;&lt;a href="#cb1-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; )&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3759 - &lt;span id="cb1-9"&gt;&lt;a href="#cb1-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3760 - &lt;p&gt;had to be converted to:&lt;/p&gt; 3761 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 3762 - class="sourceCode rust"&gt;&lt;code class="sourceCode rust"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;→ &lt;span class="kw"&gt;use&lt;/span&gt; &lt;span class="pp"&gt;serde_json::&lt;/span&gt;&lt;span class="op"&gt;{&lt;/span&gt;from_value&lt;span class="op"&gt;};&lt;/span&gt;&lt;/span&gt; 3763 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;/span&gt; 3764 - &lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="at"&gt;#[&lt;/span&gt;test&lt;span class="at"&gt;]&lt;/span&gt;&lt;/span&gt; 3765 - &lt;span id="cb2-4"&gt;&lt;a href="#cb2-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="kw"&gt;fn&lt;/span&gt; deserialize() &lt;span class="op"&gt;{&lt;/span&gt;&lt;/span&gt; 3766 - &lt;span id="cb2-5"&gt;&lt;a href="#cb2-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;assert_eq!&lt;/span&gt;(&lt;/span&gt; 3767 - &lt;span id="cb2-6"&gt;&lt;a href="#cb2-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;→ &lt;span class="pp"&gt;from_value::&lt;/span&gt;&lt;span class="op"&gt;&amp;lt;&lt;/span&gt;Action&lt;span class="op"&gt;&amp;gt;&lt;/span&gt;(&lt;span class="pp"&gt;json!&lt;/span&gt;(&lt;span class="op"&gt;{&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;set_tweak&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;highlight&amp;quot;&lt;/span&gt;&lt;span class="op"&gt;}&lt;/span&gt;))&lt;span class="op"&gt;,&lt;/span&gt;&lt;/span&gt; 3768 - &lt;span id="cb2-7"&gt;&lt;a href="#cb2-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="pp"&gt;Action::&lt;/span&gt;SetTweak(&lt;span class="pp"&gt;Tweak::&lt;/span&gt;Highlight &lt;span class="op"&gt;{&lt;/span&gt; value&lt;span class="op"&gt;:&lt;/span&gt; &lt;span class="cn"&gt;true&lt;/span&gt; &lt;span class="op"&gt;}&lt;/span&gt;)&lt;/span&gt; 3769 - &lt;span id="cb2-8"&gt;&lt;a href="#cb2-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; )&lt;span class="op"&gt;;&lt;/span&gt;&lt;/span&gt; 3770 - &lt;span id="cb2-9"&gt;&lt;a href="#cb2-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="op"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3771 - &lt;h3 id="the-arglist"&gt;The arglist&lt;/h3&gt; 3772 - &lt;p&gt;For the initial pass, I decided to handle imports, this was a simple 3773 - find and replace operation, done to all the files containing tests. 3774 - Luckily, modules (and therefore files) containing tests in Rust are 3775 - annotated with the &lt;code&gt;#[cfg(test)]&lt;/code&gt; attribute. I opened all 3776 - such files:&lt;/p&gt; 3777 - &lt;div class="sourceCode" id="cb3"&gt;&lt;pre 3778 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb3-1"&gt;&lt;a href="#cb3-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# `grep -l pattern files` lists all the files&lt;/span&gt;&lt;/span&gt; 3779 - &lt;span id="cb3-2"&gt;&lt;a href="#cb3-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# matching the pattern&lt;/span&gt;&lt;/span&gt; 3780 - &lt;span id="cb3-3"&gt;&lt;a href="#cb3-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3781 - &lt;span id="cb3-4"&gt;&lt;a href="#cb3-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;vim&lt;/span&gt; &lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="fu"&gt;grep&lt;/span&gt; &lt;span class="at"&gt;-l&lt;/span&gt; &lt;span class="st"&gt;&amp;#39;cfg\(test\)&amp;#39;&lt;/span&gt; ./&lt;span class="pp"&gt;**&lt;/span&gt;/&lt;span class="pp"&gt;*&lt;/span&gt;.rs&lt;span class="va"&gt;)&lt;/span&gt;&lt;/span&gt; 3782 - &lt;span id="cb3-5"&gt;&lt;a href="#cb3-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 3783 - &lt;span id="cb3-6"&gt;&lt;a href="#cb3-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# expands to something like:&lt;/span&gt;&lt;/span&gt; 3784 - &lt;span id="cb3-7"&gt;&lt;a href="#cb3-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="ex"&gt;vim&lt;/span&gt; push_rules.rs room/member.rs key/verification/lib.rs&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 3785 - &lt;p&gt;Starting vim with more than one file at the shell prompt populates 3786 - the arglist. Hit &lt;code&gt;:args&lt;/code&gt; to see the list of files currently 3787 - ready to edit. The square [brackets] indicate the current file. Navigate 3788 - through the arglist with &lt;code&gt;:next&lt;/code&gt; and &lt;code&gt;:prev&lt;/code&gt;. I 3789 - use tpope’s vim-unimpaired &lt;a href="#fn1" class="footnote-ref" 3790 - id="fnref1" role="doc-noteref"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;, which adds 3791 - &lt;code&gt;]a&lt;/code&gt; and &lt;code&gt;[a&lt;/code&gt;, mapped to &lt;code&gt;:next&lt;/code&gt; and 3792 - &lt;code&gt;:prev&lt;/code&gt;.&lt;/p&gt; 3793 - &lt;p&gt;All that’s left to do is the find and replace, for which we will be 3794 - using vim’s &lt;code&gt;argdo&lt;/code&gt;, applying a substitution to every file in 3795 - the arglist:&lt;/p&gt; 3796 - &lt;pre&gt;&lt;code&gt;:argdo s/from_str/from_value/g&lt;/code&gt;&lt;/pre&gt; 3797 - &lt;h3 id="the-quickfix-list"&gt;The quickfix list&lt;/h3&gt; 3798 - &lt;p&gt;Next up, replacing &lt;code&gt;r#" ... "#&lt;/code&gt; with 3799 - &lt;code&gt;json!( ... )&lt;/code&gt;. I couldn’t search and replace that trivially, 3800 - so I went with a macro call &lt;a href="#fn2" class="footnote-ref" 3801 - id="fnref2" role="doc-noteref"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; instead, starting with 3802 - the cursor on ‘r’, represented by the caret, in my attempt to breakdown 3803 - the process:&lt;/p&gt; 3804 - &lt;pre&gt;&lt;code&gt;BUFFER: r#&amp;quot; ... &amp;quot;#; 3805 - ^ 3806 - 3807 - ACTION: vllsjson!( 3808 - 3809 - BUFFER json!( ... &amp;quot;#; 3810 - ^ 3811 - 3812 - ACTION: &amp;lt;esc&amp;gt;$F# 3813 - 3814 - BUFFER: json!( ... &amp;quot;#; 3815 - ^ 3816 - 3817 - ACTION: vhs)&amp;lt;esc&amp;gt; 3818 - 3819 - BUFFER: json!( ... );&lt;/code&gt;&lt;/pre&gt; 3820 - &lt;p&gt;Here’s the recorded &lt;a href="#fn3" class="footnote-ref" id="fnref3" 3821 - role="doc-noteref"&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt; macro in all its glory: 3822 - &lt;code&gt;vllsjson!(&amp;lt;esc&amp;gt;$F#vhs)&amp;lt;esc&amp;gt;&lt;/code&gt;.&lt;/p&gt; 3823 - &lt;p&gt;Great! So now we just go ahead, find every occurrence of 3824 - &lt;code&gt;r#&lt;/code&gt; and apply the macro right? Unfortunately, there were 3825 - more than a few occurrences of raw strings that had to stay raw strings. 3826 - Enter, the quickfix list.&lt;/p&gt; 3827 - &lt;p&gt;The idea behind the quickfix list is to jump from one position in a 3828 - file to another (maybe in a different file), much like how the arglist 3829 - lets you jump from one file to another.&lt;/p&gt; 3830 - &lt;p&gt;One of the easiest ways to populate this list with a bunch of 3831 - positions is to use &lt;code&gt;vimgrep&lt;/code&gt;:&lt;/p&gt; 3832 - &lt;pre&gt;&lt;code&gt;# basic usage 3833 - :vimgrep pattern files 3834 - 3835 - # search for raw strings 3836 - :vimgrep &amp;#39;r#&amp;#39; ./**/*.rs&lt;/code&gt;&lt;/pre&gt; 3837 - &lt;p&gt;Like &lt;code&gt;:next&lt;/code&gt; and &lt;code&gt;:prev&lt;/code&gt;, you can navigate the 3838 - quickfix list with &lt;code&gt;:cnext&lt;/code&gt; and &lt;code&gt;:cprev&lt;/code&gt;. Every 3839 - time you move up or down the list, vim indicates your index:&lt;/p&gt; 3840 - &lt;pre&gt;&lt;code&gt;(1 of 131): r#&amp;quot;{&amp;quot;set_tweak&amp;quot;: &amp;quot;highlight&amp;quot;}&amp;quot;#;&lt;/code&gt;&lt;/pre&gt; 3841 - &lt;p&gt;And just like &lt;code&gt;argdo&lt;/code&gt;, you can &lt;code&gt;cdo&lt;/code&gt; to apply 3842 - commands to &lt;em&gt;every&lt;/em&gt; match in the quickfix list:&lt;/p&gt; 3843 - &lt;pre&gt;&lt;code&gt;:cdo norm! @q&lt;/code&gt;&lt;/pre&gt; 3844 - &lt;p&gt;But, I had to manually pick out matches, and it involved some button 3845 - mashing.&lt;/p&gt; 3846 - &lt;h3 id="external-filtering"&gt;External Filtering&lt;/h3&gt; 3847 - &lt;p&gt;Some code reviews later, I was asked to format all the json inside 3848 - the &lt;code&gt;json!&lt;/code&gt; macro. All you have to do is pass a visual 3849 - selection through a pretty json printer. Select the range to be 3850 - formatted in visual mode, and hit &lt;code&gt;:&lt;/code&gt;, you will notice the 3851 - command line displaying what seems to be gibberish:&lt;/p&gt; 3852 - &lt;pre&gt;&lt;code&gt;:&amp;#39;&amp;lt;,&amp;#39;&amp;gt;&lt;/code&gt;&lt;/pre&gt; 3853 - &lt;p&gt;&lt;code&gt;'&amp;lt;&lt;/code&gt; and &lt;code&gt;'&amp;gt;&lt;/code&gt; are &lt;em&gt;marks&lt;/em&gt; &lt;a 3854 - href="#fn4" class="footnote-ref" id="fnref4" 3855 - role="doc-noteref"&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt;. More specifically, they are marks 3856 - that vim sets automatically every time you make a visual selection, 3857 - denoting the start and end of the selection.&lt;/p&gt; 3858 - &lt;p&gt;A range is one or more line specifiers separated by a 3859 - &lt;code&gt;,&lt;/code&gt;:&lt;/p&gt; 3860 - &lt;pre&gt;&lt;code&gt;:1,7 lines 1 through 7 3861 - :32 just line 32 3862 - :. the current line 3863 - :.,$ the current line to the last line 3864 - :&amp;#39;a,&amp;#39;b mark &amp;#39;a&amp;#39; to mark &amp;#39;b&amp;#39;&lt;/code&gt;&lt;/pre&gt; 3865 - &lt;p&gt;Most &lt;code&gt;:&lt;/code&gt; commands can be prefixed by ranges. 3866 - &lt;code&gt;:help usr_10.txt&lt;/code&gt; for more on that.&lt;/p&gt; 3867 - &lt;p&gt;Alright, lets pass json through &lt;code&gt;python -m json.tool&lt;/code&gt;, a 3868 - json formatter that accepts &lt;code&gt;stdin&lt;/code&gt; (note the use of 3869 - &lt;code&gt;!&lt;/code&gt; to make use of an external program):&lt;/p&gt; 3870 - &lt;pre&gt;&lt;code&gt;:&amp;#39;&amp;lt;,&amp;#39;&amp;gt;!python -m json.tool&lt;/code&gt;&lt;/pre&gt; 3871 - &lt;p&gt;Unfortunately that didn’t quite work for me because the range 3872 - included some non-json text as well, a mix of regex and macros helped 3873 - fix that. I think you get the drift.&lt;/p&gt; 3874 - &lt;p&gt;Another fun filter I use from time to time is &lt;code&gt;:!sort&lt;/code&gt;, to 3875 - sort css attributes, or &lt;code&gt;:!uniq&lt;/code&gt; to remove repeated 3876 - imports.&lt;/p&gt; 3877 - &lt;section id="footnotes" class="footnotes footnotes-end-of-document" 3878 - role="doc-endnotes"&gt; 3879 - &lt;hr /&gt; 3880 - &lt;ol&gt; 3881 - &lt;li id="fn1"&gt;&lt;p&gt;https://github.com/tpope/vim-unimpaired It also handles 3882 - various other mappings, &lt;code&gt;]q&lt;/code&gt; and &lt;code&gt;[q&lt;/code&gt; to navigate 3883 - the quickfix list for example&lt;a href="#fnref1" class="footnote-back" 3884 - role="doc-backlink"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt; 3885 - &lt;li id="fn2"&gt;&lt;p&gt;&lt;code&gt;:help recording&lt;/code&gt;&lt;a href="#fnref2" 3886 - class="footnote-back" role="doc-backlink"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt; 3887 - &lt;li id="fn3"&gt;&lt;p&gt;When I’m recording a macro, I prefer starting out by 3888 - storing it in register &lt;code&gt;q&lt;/code&gt;, and then copying it over to 3889 - another register if it works as intended. I think of &lt;code&gt;qq&lt;/code&gt; as 3890 - ‘quick record’.&lt;a href="#fnref3" class="footnote-back" 3891 - role="doc-backlink"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt; 3892 - &lt;li id="fn4"&gt;&lt;p&gt;&lt;code&gt;:help mark-motions&lt;/code&gt;&lt;a href="#fnref4" 3893 - class="footnote-back" role="doc-backlink"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt; 3894 - &lt;/ol&gt; 3895 - &lt;/section&gt;</description> 3896 - <link>https://oppi.li/posts/rapid_refactoring_with_vim/</link> 3897 - <pubDate>Tue, 31 Mar 2020 18:30:00 +0000</pubDate> 3898 - <guid>https://oppi.li/posts/rapid_refactoring_with_vim/</guid> 3899 - </item> 3900 - <item> 3901 - <title>Font Size Fallacies</title> 3902 - <description>&lt;p&gt;I am not an expert with fonts, but I do have some experience &lt;a 3903 - href="#fn1" class="footnote-ref" id="fnref1" 3904 - role="doc-noteref"&gt;&lt;sup&gt;1&lt;/sup&gt;&lt;/a&gt;, and common sense. This post aims to 3905 - debunk some misconceptions about font sizes!&lt;/p&gt; 3906 - &lt;p&gt;11 px on your display is &lt;em&gt;probably not&lt;/em&gt; 11 px on my display. 3907 - Let’s do some quick math. I have two displays, 1366x768 @ 21” and 3908 - another with 1920x1080 @ 13”, call them &lt;code&gt;A&lt;/code&gt; and 3909 - &lt;code&gt;B&lt;/code&gt; for now.&lt;/p&gt; 3910 - &lt;p&gt;Display &lt;code&gt;A&lt;/code&gt; has 1,049,088 pixels. A pixel is a square, of 3911 - side say, &lt;code&gt;s&lt;/code&gt; cm. The total area covered by my 21” display is 3912 - about 1,066 cm^2 (41x26). Thus,&lt;/p&gt; 3913 - &lt;pre&gt;&lt;code&gt;Display A 3914 - Dimensions: 1366x768 @ 21&amp;quot; (41x26 sq. cm) 3915 - 1,049,088 s^2 = 1066 3916 - s = 0.0318 cm (side of a pixel on Display A)&lt;/code&gt;&lt;/pre&gt; 3917 - &lt;p&gt;Bear with me, as I repeat the number crunching for Display 3918 - &lt;code&gt;B&lt;/code&gt;:&lt;/p&gt; 3919 - &lt;pre&gt;&lt;code&gt;Display B 3920 - Dimensions: 1920x1080 @ 13&amp;quot; (29.5x16.5 sq. cm) 3921 - 2,073,600 s^2 = 486.75 3922 - s = 0.0153 cm (side of a pixel on Display B)&lt;/code&gt;&lt;/pre&gt; 3923 - &lt;p&gt;The width of a pixel on Display &lt;code&gt;A&lt;/code&gt; is &lt;em&gt;double&lt;/em&gt; the 3924 - width of a pixel on Display &lt;code&gt;B&lt;/code&gt;. The area occupied by a pixel 3925 - on Display &lt;code&gt;A&lt;/code&gt; is &lt;em&gt;4 times&lt;/em&gt; the area occupied by a 3926 - pixel on Display &lt;code&gt;B&lt;/code&gt;.&lt;/p&gt; 3927 - &lt;p&gt;&lt;em&gt;The size of a pixel varies from display to display!&lt;/em&gt;&lt;/p&gt; 3928 - &lt;p&gt;A 5x11 bitmap font on Display &lt;code&gt;A&lt;/code&gt; would be around 4 mm 3929 - tall whereas the same bitmap font on Display &lt;code&gt;B&lt;/code&gt; would be 3930 - around 1.9 mm tall. A 11 px tall character on &lt;code&gt;B&lt;/code&gt; is visually 3931 - equivalent to a 5 px character on &lt;code&gt;A&lt;/code&gt;. When you view a 3932 - screenshot of Display &lt;code&gt;A&lt;/code&gt; on Display &lt;code&gt;B&lt;/code&gt;, the 3933 - contents are shrunk down by a factor of 2!&lt;/p&gt; 3934 - &lt;p&gt;So screen resolution is not enough, how else do we measure size? 3935 - Pixel Density! Keen readers will realize that the 5^th grade math 3936 - problem we solved up there showcases pixel density, or, pixels per cm 3937 - (PPCM). Usually we deal with pixels per inch (PPI).&lt;/p&gt; 3938 - &lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; PPI is not to be confused with DPI &lt;a 3939 - href="#fn2" class="footnote-ref" id="fnref2" 3940 - role="doc-noteref"&gt;&lt;sup&gt;2&lt;/sup&gt;&lt;/a&gt; (dots per inch). DPI is defined for 3941 - printers.&lt;/p&gt; 3942 - &lt;p&gt;In our example, &lt;code&gt;A&lt;/code&gt; is a 75 ppi display and &lt;code&gt;B&lt;/code&gt; 3943 - is around 165 ppi &lt;a href="#fn3" class="footnote-ref" id="fnref3" 3944 - role="doc-noteref"&gt;&lt;sup&gt;3&lt;/sup&gt;&lt;/a&gt;. A low ppi display appears to be 3945 - ‘pixelated’, because the pixels are more prominent, much like Display 3946 - &lt;code&gt;A&lt;/code&gt;. A higher ppi usually means you can view larger images 3947 - and render crispier fonts. The average desktop display can stuff 100-200 3948 - pixels per inch. Smart phones usually fall into the 400-600 ppi 3949 - (XXXHDPI) category. The human eye fails to differentiate detail past 300 3950 - ppi.&lt;/p&gt; 3951 - &lt;p&gt;&lt;em&gt;So … streaming an 8K video on a 60” TV provides the same clarity 3952 - as a HD video on a smart phone?&lt;/em&gt;&lt;/p&gt; 3953 - &lt;p&gt;Absolutely. Well, clarity is subjective, but the amount of detail you 3954 - can discern on mobile displays has always been limited. Salty consumers 3955 - of the Xperia 1 &lt;a href="#fn4" class="footnote-ref" id="fnref4" 3956 - role="doc-noteref"&gt;&lt;sup&gt;4&lt;/sup&gt;&lt;/a&gt; will say otherwise.&lt;/p&gt; 3957 - &lt;p&gt;Maybe I will talk about font rendering in another post, but thats all 3958 - for now. Don’t judge a font size by its screenshot.&lt;/p&gt; 3959 - &lt;section id="footnotes" class="footnotes footnotes-end-of-document" 3960 - role="doc-endnotes"&gt; 3961 - &lt;hr /&gt; 3962 - &lt;ol&gt; 3963 - &lt;li id="fn1"&gt;&lt;p&gt;https://github.com/nerdypepper/scientifica&lt;a 3964 - href="#fnref1" class="footnote-back" role="doc-backlink"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt; 3965 - &lt;li id="fn2"&gt;&lt;p&gt;https://en.wikipedia.org/wiki/Dots_per_inch&lt;a 3966 - href="#fnref2" class="footnote-back" role="doc-backlink"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt; 3967 - &lt;li id="fn3"&gt;&lt;p&gt;https://www.sven.de/dpi/&lt;a href="#fnref3" 3968 - class="footnote-back" role="doc-backlink"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt; 3969 - &lt;li id="fn4"&gt;&lt;p&gt;https://en.wikipedia.org/wiki/Sony_Xperia_1&lt;a 3970 - href="#fnref4" class="footnote-back" role="doc-backlink"&gt;↩︎&lt;/a&gt;&lt;/p&gt;&lt;/li&gt; 3971 - &lt;/ol&gt; 3972 - &lt;/section&gt;</description> 3973 - <link>https://oppi.li/posts/font_size_fallacies/</link> 3974 - <pubDate>Mon, 16 Mar 2020 18:30:00 +0000</pubDate> 3975 - <guid>https://oppi.li/posts/font_size_fallacies/</guid> 3976 - </item> 3977 - <item> 3978 - <title>Termux Tandem</title> 3979 - <description>&lt;p&gt;I learnt about &lt;code&gt;termux&lt;/code&gt; from a friend on IRC recently. It 3980 - looked super gimmicky to me at first, but it eventually proved to be 3981 - useful. Here’s what I use it for:&lt;/p&gt; 3982 - &lt;h3 id="rsync"&gt;rsync&lt;/h3&gt; 3983 - &lt;p&gt;Ever since I degoogled my android device, syncing files between my 3984 - phone and my PC has always been a pain. I’m looking at you MTP. But, 3985 - with &lt;code&gt;termux&lt;/code&gt; and &lt;code&gt;sshd&lt;/code&gt; all set up, it’s as 3986 - simple as:&lt;/p&gt; 3987 - &lt;pre&gt;&lt;code&gt;$ arp 3988 - Address HWtype HWad ... 3989 - 192.168.43.187 ether d0:0 ... 3990 - 3991 - $ rsync -avz 192.168.43.187:~/frogs ~/pics/frogs&lt;/code&gt;&lt;/pre&gt; 3992 - &lt;h3 id="ssh-tmux"&gt;ssh &amp;amp; tmux&lt;/h3&gt; 3993 - &lt;p&gt;My phone doubles as a secondary view into my main machine with 3994 - &lt;code&gt;ssh&lt;/code&gt; and &lt;code&gt;tmux&lt;/code&gt;. When I am away from my PC (read: 3995 - sitting across the room), I check build status and IRC messages by 3996 - &lt;code&gt;ssh&lt;/code&gt;ing into a tmux session running the said build or 3997 - weechat.&lt;/p&gt; 3998 - &lt;h3 id="file-uploads"&gt;file uploads&lt;/h3&gt; 3999 - &lt;p&gt;Not being able to access my (ssh-only) file host was crippling. With 4000 - a &lt;code&gt;bash&lt;/code&gt; instance on my phone, I just copied over my ssh 4001 - keys, and popped in a file upload script (a glorified &lt;code&gt;scp&lt;/code&gt;). 4002 - Now I just have to figure out a way to clean up these file names …&lt;/p&gt; 4003 - &lt;pre&gt;&lt;code&gt;~/storage/pictures/ $ ls 4004 - 02muf5g7b2i41.jpg 7alt3cwg77841.jpg cl4bsrge7id11.png 4005 - mtZabXG.jpg p8d5c584f2841.jpg vjUxGjq.jpg&lt;/code&gt;&lt;/pre&gt; 4006 - &lt;h3 id="cmus"&gt;cmus&lt;/h3&gt; 4007 - &lt;p&gt;Alright, I don’t really listen to music via &lt;code&gt;cmus&lt;/code&gt;, but I 4008 - did use it a couple times when my default music player was acting up. 4009 - &lt;code&gt;cmus&lt;/code&gt; is a viable option:&lt;/p&gt; 4010 - &lt;p&gt;&lt;a href="https://u.peppe.rs/CP.jpg"&gt;&lt;img 4011 - src="https://u.peppe.rs/CP.jpg" /&gt;&lt;/a&gt;&lt;/p&gt;</description> 4012 - <link>https://oppi.li/posts/termux_tandem/</link> 4013 - <pubDate>Sat, 07 Mar 2020 18:30:00 +0000</pubDate> 4014 - <guid>https://oppi.li/posts/termux_tandem/</guid> 4015 - </item> 4016 - <item> 4017 - <title>Call To ARMs</title> 4018 - <description>&lt;p&gt;My 4th semester involves ARM programming. And proprietary tooling 4019 - (Keil C). But we don’t do that here.&lt;/p&gt; 4020 - &lt;h3 id="building"&gt;Building&lt;/h3&gt; 4021 - &lt;p&gt;Assembling and linking ARM binaries on non-ARM architecture devices 4022 - is fairly trivial. I went along with the GNU cross bare metal toolchain 4023 - binutils, which provides &lt;code&gt;arm-as&lt;/code&gt; and &lt;code&gt;arm-ld&lt;/code&gt; 4024 - (among a bunch of other utils that I don’t care about for now).&lt;/p&gt; 4025 - &lt;p&gt;Assemble &lt;code&gt;.s&lt;/code&gt; files with:&lt;/p&gt; 4026 - &lt;pre class="shell"&gt;&lt;code&gt;arm-none-eabi-as main.s -g -march=armv8.1-a -o main.out&lt;/code&gt;&lt;/pre&gt; 4027 - &lt;p&gt;The &lt;code&gt;-g&lt;/code&gt; flag generates extra debugging information that 4028 - &lt;code&gt;gdb&lt;/code&gt; picks up. The &lt;code&gt;-march&lt;/code&gt; option establishes 4029 - target architecture.&lt;/p&gt; 4030 - &lt;p&gt;Link &lt;code&gt;.o&lt;/code&gt; files with:&lt;/p&gt; 4031 - &lt;pre class="shell"&gt;&lt;code&gt;arm-none-eabi-ld main.out -o main&lt;/code&gt;&lt;/pre&gt; 4032 - &lt;h3 id="running-and-debugging"&gt;Running (and Debugging)&lt;/h3&gt; 4033 - &lt;p&gt;Things get interesting here. &lt;code&gt;gdb&lt;/code&gt; on your x86 machine 4034 - cannot read nor execute binaries compiled for ARM. So, we simulate an 4035 - ARM processor using &lt;code&gt;qemu&lt;/code&gt;. Now qemu allows you to run 4036 - &lt;code&gt;gdbserver&lt;/code&gt; on startup. Connecting our local &lt;code&gt;gdb&lt;/code&gt; 4037 - instance to &lt;code&gt;gdbserver&lt;/code&gt; gives us a view into the program’s 4038 - execution. Easy!&lt;/p&gt; 4039 - &lt;p&gt;Run &lt;code&gt;qemu&lt;/code&gt;, with &lt;code&gt;gdbserver&lt;/code&gt; on port 4040 - &lt;code&gt;1234&lt;/code&gt;, with our ARM binary, &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt; 4041 - &lt;pre class="shell"&gt;&lt;code&gt;qemu-arm -singlestep -g 1234 main&lt;/code&gt;&lt;/pre&gt; 4042 - &lt;p&gt;Start up &lt;code&gt;gdb&lt;/code&gt; on your machine, and connect to 4043 - &lt;code&gt;qemu&lt;/code&gt;’s &lt;code&gt;gdbserver&lt;/code&gt;:&lt;/p&gt; 4044 - &lt;pre&gt;&lt;code&gt;(gdb) set architecture armv8-a 4045 - (gdb) target remote localhost:1234 4046 - (gdb) file main 4047 - Reading symbols from main... # yay!&lt;/code&gt;&lt;/pre&gt; 4048 - &lt;h3 id="gdb-enhanced"&gt;GDB Enhanced&lt;/h3&gt; 4049 - &lt;p&gt;&lt;code&gt;gdb&lt;/code&gt; is cool, but it’s not nearly as comfortable as well 4050 - fleshed out emulators/IDEs like Keil. Watching registers, CPSR and 4051 - memory chunks update &lt;em&gt;is&lt;/em&gt; pretty fun.&lt;/p&gt; 4052 - &lt;p&gt;I came across &lt;code&gt;gdb&lt;/code&gt;’s TUI mode (hit &lt;code&gt;C-x C-a&lt;/code&gt; 4053 - or type &lt;code&gt;tui enable&lt;/code&gt; at the prompt). TUI mode is a godsend. 4054 - It highlights the current line of execution, shows you disassembly 4055 - outputs, updated registers, active breakpoints and more.&lt;/p&gt; 4056 - &lt;p&gt;&lt;em&gt;But&lt;/em&gt;, it is an absolute eyesore.&lt;/p&gt; 4057 - &lt;p&gt;Say hello to &lt;a href="https://github.com/hugsy/gef"&gt;GEF&lt;/a&gt;! “GDB 4058 - Enhanced Features” teaches our old dog some cool new tricks. Here are 4059 - some additions that made my ARM debugging experience loads better:&lt;/p&gt; 4060 - &lt;ul&gt; 4061 - &lt;li&gt;Memory watches&lt;/li&gt; 4062 - &lt;li&gt;Register watches, with up to 7 levels of deref (overkill, I 4063 - agree)&lt;/li&gt; 4064 - &lt;li&gt;Stack tracing&lt;/li&gt; 4065 - &lt;/ul&gt; 4066 - &lt;p&gt;And it’s pretty! See for yourself:&lt;/p&gt; 4067 - &lt;p&gt;&lt;a href="https://u.peppe.rs/wq.png"&gt;&lt;img 4068 - src="https://u.peppe.rs/wq.png" /&gt;&lt;/a&gt;&lt;/p&gt; 4069 - &lt;h3 id="editing"&gt;Editing&lt;/h3&gt; 4070 - &lt;p&gt;Vim, with &lt;code&gt;syntax off&lt;/code&gt; because it dosen’t handle GNU ARM 4071 - syntax too well.&lt;/p&gt;</description> 4072 - <link>https://oppi.li/posts/call_to_ARMs/</link> 4073 - <pubDate>Fri, 07 Feb 2020 18:30:00 +0000</pubDate> 4074 - <guid>https://oppi.li/posts/call_to_ARMs/</guid> 4075 - </item> 4076 - <item> 4077 - <title>Color Conundrum</title> 4078 - <description>&lt;p&gt;This piece aims to highlight (pun intended) some of the reasons 4079 - behind my &lt;a href="https://u.peppe.rs/bF.png"&gt;color free&lt;/a&gt; editor 4080 - setup.&lt;/p&gt; 4081 - &lt;p&gt;Imagine highlighting an entire book because &lt;em&gt;all&lt;/em&gt; of it is 4082 - important. That is exactly what (most) syntax highlighting does. It is 4083 - difficult for the human eye to filter out noise in rainbow barf. Use 4084 - color to draw attention, not diverge it.&lt;/p&gt; 4085 - &lt;p&gt;At the same time, a book devoid of color is &lt;em&gt;boring!&lt;/em&gt; What is 4086 - the takeaway from this 10 line paragraph? What are the technical terms 4087 - used?&lt;/p&gt; 4088 - &lt;p&gt;Prose and code are certainly different, but the fickle minded human 4089 - eye is the same. The eye constantly looks for a frame of reference, a 4090 - focal point. It grows tired when it can’t find one.&lt;/p&gt; 4091 - &lt;p&gt;The following comparison does a better job of explaining (none, ample 4092 - and over-the-top highlighting, from left to right):&lt;/p&gt; 4093 - &lt;p&gt;&lt;a href="https://u.peppe.rs/lt.png"&gt;&lt;img 4094 - src="https://u.peppe.rs/lt.png" /&gt;&lt;/a&gt;&lt;/p&gt; 4095 - &lt;p&gt;Without highlighting (far left), it is hard to differentiate between 4096 - comments and code! The florid color scheme (far right) is no good 4097 - either, it contains too many attention grabbers. The center sample is a 4098 - healthy balance of both. Function calls and constants stand out, and 4099 - repetitive keywords and other noise (&lt;code&gt;let&lt;/code&gt;, &lt;code&gt;as&lt;/code&gt;) 4100 - are mildly dimmed out. Comments and non-code text (sign column, status 4101 - text) are dimmed further.&lt;/p&gt; 4102 - &lt;p&gt;I’ll stop myself before I rant about color contrast and 4103 - combinations.&lt;/p&gt;</description> 4104 - <link>https://oppi.li/posts/color_conundrum/</link> 4105 - <pubDate>Mon, 30 Dec 2019 18:30:00 +0000</pubDate> 4106 - <guid>https://oppi.li/posts/color_conundrum/</guid> 4107 - </item> 4108 - <item> 4109 - <title>Static Sites With Bash</title> 4110 - <description>&lt;p&gt;After going through a bunch of static site generators (&lt;a 4111 - href="https://blog.getpelican.com/"&gt;pelican&lt;/a&gt;, &lt;a 4112 - href="https://gohugo.io"&gt;hugo&lt;/a&gt;, &lt;a 4113 - href="https://github.com/icyphox/vite"&gt;vite&lt;/a&gt;), I decided to roll my 4114 - own. If you are more of the ‘show me the code’ kinda guy, &lt;a 4115 - href="https://github.com/nerdypepper/site"&gt;here&lt;/a&gt; you go.&lt;/p&gt; 4116 - &lt;h3 id="text-formatting"&gt;Text formatting&lt;/h3&gt; 4117 - &lt;p&gt;I chose to write in markdown, and convert to html with &lt;a 4118 - href="https://kristaps.bsd.lv/lowdown/"&gt;lowdown&lt;/a&gt;.&lt;/p&gt; 4119 - &lt;h3 id="directory-structure"&gt;Directory structure&lt;/h3&gt; 4120 - &lt;p&gt;I host my site on GitHub pages, so &lt;code&gt;docs/&lt;/code&gt; has to be the 4121 - entry point. Markdown formatted posts go into &lt;code&gt;posts/&lt;/code&gt;, get 4122 - converted into html, and end up in &lt;code&gt;docs/index.html&lt;/code&gt;, 4123 - something like this:&lt;/p&gt; 4124 - &lt;div class="sourceCode" id="cb1"&gt;&lt;pre 4125 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb1-1"&gt;&lt;a href="#cb1-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;posts&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="fu"&gt;ls&lt;/span&gt; &lt;span class="at"&gt;-t&lt;/span&gt; ./posts&lt;span class="va"&gt;)&lt;/span&gt; &lt;span class="co"&gt;# chronological order!&lt;/span&gt;&lt;/span&gt; 4126 - &lt;span id="cb1-2"&gt;&lt;a href="#cb1-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;for&lt;/span&gt; f &lt;span class="kw"&gt;in&lt;/span&gt; &lt;span class="va"&gt;$posts&lt;/span&gt;&lt;span class="kw"&gt;;&lt;/span&gt; &lt;span class="cf"&gt;do&lt;/span&gt;&lt;/span&gt; 4127 - &lt;span id="cb1-3"&gt;&lt;a href="#cb1-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;file&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;./posts/&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$f&lt;/span&gt; &lt;span class="co"&gt;# `ls` mangled our file paths&lt;/span&gt;&lt;/span&gt; 4128 - &lt;span id="cb1-4"&gt;&lt;a href="#cb1-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;generating post &lt;/span&gt;&lt;span class="va"&gt;$file&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; 4129 - &lt;span id="cb1-5"&gt;&lt;a href="#cb1-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 4130 - &lt;span id="cb1-6"&gt;&lt;a href="#cb1-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="va"&gt;html&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt;&lt;span class="ex"&gt;lowdown&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$file&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;)&lt;/span&gt;&lt;/span&gt; 4131 - &lt;span id="cb1-7"&gt;&lt;a href="#cb1-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt; &lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="at"&gt;-e&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;html&amp;quot;&lt;/span&gt; &lt;span class="op"&gt;&amp;gt;&amp;gt;&lt;/span&gt; docs/index.html&lt;/span&gt; 4132 - &lt;span id="cb1-8"&gt;&lt;a href="#cb1-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="cf"&gt;done&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 4133 - &lt;h3 id="assets"&gt;Assets&lt;/h3&gt; 4134 - &lt;p&gt;Most static site generators recommend dropping image assets into the 4135 - site source itself. That does have it’s merits, but I prefer hosting 4136 - images separately:&lt;/p&gt; 4137 - &lt;div class="sourceCode" id="cb2"&gt;&lt;pre 4138 - class="sourceCode bash"&gt;&lt;code class="sourceCode bash"&gt;&lt;span id="cb2-1"&gt;&lt;a href="#cb2-1" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# strip file extension&lt;/span&gt;&lt;/span&gt; 4139 - &lt;span id="cb2-2"&gt;&lt;a href="#cb2-2" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;ext&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;${1&lt;/span&gt;&lt;span class="op"&gt;##&lt;/span&gt;&lt;span class="pp"&gt;*&lt;/span&gt;.&lt;span class="va"&gt;}&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; 4140 - &lt;span id="cb2-3"&gt;&lt;a href="#cb2-3" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 4141 - &lt;span id="cb2-4"&gt;&lt;a href="#cb2-4" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# generate a random file name&lt;/span&gt;&lt;/span&gt; 4142 - &lt;span id="cb2-5"&gt;&lt;a href="#cb2-5" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;id&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="va"&gt;$(&lt;/span&gt; &lt;span class="fu"&gt;cat&lt;/span&gt; /dev/urandom &lt;span class="kw"&gt;|&lt;/span&gt; &lt;span class="fu"&gt;tr&lt;/span&gt; &lt;span class="at"&gt;-dc&lt;/span&gt; &lt;span class="st"&gt;&amp;#39;a-zA-Z0-9&amp;#39;&lt;/span&gt; &lt;span class="kw"&gt;|&lt;/span&gt; &lt;span class="fu"&gt;fold&lt;/span&gt; &lt;span class="at"&gt;-w&lt;/span&gt; 2 &lt;span class="kw"&gt;|&lt;/span&gt; &lt;span class="fu"&gt;head&lt;/span&gt; &lt;span class="at"&gt;-n&lt;/span&gt; 1 &lt;span class="va"&gt;)&lt;/span&gt;&lt;/span&gt; 4143 - &lt;span id="cb2-6"&gt;&lt;a href="#cb2-6" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="va"&gt;id&lt;/span&gt;&lt;span class="op"&gt;=&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$id&lt;/span&gt;&lt;span class="st"&gt;.&lt;/span&gt;&lt;span class="va"&gt;$ext&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt; 4144 - &lt;span id="cb2-7"&gt;&lt;a href="#cb2-7" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;/span&gt; 4145 - &lt;span id="cb2-8"&gt;&lt;a href="#cb2-8" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="co"&gt;# copy to my file host&lt;/span&gt;&lt;/span&gt; 4146 - &lt;span id="cb2-9"&gt;&lt;a href="#cb2-9" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="fu"&gt;scp&lt;/span&gt; &lt;span class="at"&gt;-P&lt;/span&gt; 443 &lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$1&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; emerald:files/&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;span class="va"&gt;$id&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt; &lt;/span&gt; 4147 - &lt;span id="cb2-10"&gt;&lt;a href="#cb2-10" aria-hidden="true" tabindex="-1"&gt;&lt;/a&gt;&lt;span class="bu"&gt;echo&lt;/span&gt; &lt;span class="st"&gt;&amp;quot;https://u.peppe.rs/&lt;/span&gt;&lt;span class="va"&gt;$id&lt;/span&gt;&lt;span class="st"&gt;&amp;quot;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt; 4148 - &lt;h3 id="templating"&gt;Templating&lt;/h3&gt; 4149 - &lt;p&gt;&lt;a 4150 - href="https://github.com/NerdyPepper/site/blob/master/generate.sh"&gt;&lt;code&gt;generate.sh&lt;/code&gt;&lt;/a&gt; 4151 - brings the above bits and pieces together (with some extra cruft to 4152 - avoid javascript). It uses &lt;code&gt;sed&lt;/code&gt; to produce nice titles from 4153 - the file names (removes underscores, title-case), and 4154 - &lt;code&gt;date(1)&lt;/code&gt; to add the date to each post listing!&lt;/p&gt;</description> 4155 - <link>https://oppi.li/posts/static_sites_with_bash/</link> 4156 - <pubDate>Fri, 22 Nov 2019 18:30:00 +0000</pubDate> 4157 - <guid>https://oppi.li/posts/static_sites_with_bash/</guid> 4158 - </item> 4159 - <item> 4160 - <title>My Setup</title> 4161 - <description>&lt;p&gt;Decided to do one of these because everyone does one of these.&lt;/p&gt; 4162 - &lt;p&gt;&lt;img src="https://u.peppe.rs/Hb.png" /&gt;&lt;/p&gt; 4163 - &lt;p&gt;My entire setup is managed with GNU &lt;code&gt;stow&lt;/code&gt;, making it 4164 - easier to replicate on fresh installations. You can find my 4165 - configuration files on &lt;a 4166 - href="https://github.com/nerdypepper"&gt;GitHub&lt;/a&gt;.&lt;/p&gt; 4167 - &lt;p&gt;I run Void Linux (glibc) on my &lt;a 4168 - href="https://store.hp.com/us/en/mdp/laptops/envy-13"&gt;HP Envy 13” 4169 - (2018)&lt;/a&gt;. To keep things simple, I run a raw X session with 4170 - &lt;code&gt;2bwm&lt;/code&gt; as my window manager, along with &lt;code&gt;dunst&lt;/code&gt; 4171 - (notification daemon) and Sam’s &lt;a 4172 - href="https://github.com/sdhand/compton"&gt;&lt;code&gt;compton&lt;/code&gt;&lt;/a&gt; 4173 - (compositor) fork.&lt;/p&gt; 4174 - &lt;p&gt;I am a fan of GNU tools, so I use &lt;code&gt;bash&lt;/code&gt; as my shell, and 4175 - &lt;code&gt;coreutils&lt;/code&gt; to manage files, archives, strings, paths etc. I 4176 - edit files with &lt;code&gt;vim&lt;/code&gt;, chat with &lt;code&gt;weechat&lt;/code&gt;, listen 4177 - to music with &lt;code&gt;cmus&lt;/code&gt;, monitor processes with 4178 - &lt;code&gt;htop&lt;/code&gt;, manage sessions with &lt;code&gt;tmux&lt;/code&gt;, read pdfs in 4179 - &lt;code&gt;zathura&lt;/code&gt;. I rarely ever leave the comfort of my terminal 4180 - emulator, &lt;code&gt;urxvt&lt;/code&gt;.&lt;/p&gt; 4181 - &lt;p&gt;Most of my academic typesetting is done with TeX, and compiled with 4182 - &lt;code&gt;xelatex&lt;/code&gt;. Other &lt;em&gt;fun&lt;/em&gt; documents are made with GIMP 4183 - :).&lt;/p&gt;</description> 4184 - <link>https://oppi.li/posts/my_setup/</link> 4185 - <pubDate>Wed, 06 Nov 2019 18:30:00 +0000</pubDate> 4186 - <guid>https://oppi.li/posts/my_setup/</guid> 4187 - </item> 4188 - <item> 4189 - <title>WPA Woes</title> 4190 - <description>&lt;p&gt;I finally got around to installing Void GNU/Linux on my main 4191 - computer. Rolling release, non-systemd, need I say more?&lt;/p&gt; 4192 - &lt;p&gt;As with all GNU/Linux distributions, wireless networks had me in a 4193 - fix. If you can see this post, it means I’ve managed to get online. It 4194 - turns out, &lt;code&gt;wpa_supplicant&lt;/code&gt; was detecting the wrong interface 4195 - by default (does it ever select the right one?). Let us fix that:&lt;/p&gt; 4196 - &lt;pre&gt;&lt;code&gt;$ sudo rm -r /var/service/wpa_supplicant 4197 - $ sudo killall dhcpcd&lt;/code&gt;&lt;/pre&gt; 4198 - &lt;p&gt;What is the right interface though?&lt;/p&gt; 4199 - &lt;pre&gt;&lt;code&gt;$ iw dev 4200 - ... 4201 - Interface wlp2s0 4202 - ...&lt;/code&gt;&lt;/pre&gt; 4203 - &lt;p&gt;Aha! Let us run &lt;code&gt;wpa_supplicant&lt;/code&gt; on that interface, as a 4204 - background process:&lt;/p&gt; 4205 - &lt;pre&gt;&lt;code&gt;$ sudo wpa_supplicant -B -i wlp2s0 -c /etc/wpa_supplicant/wpa_supplicant.conf 4206 - $ sudo dhcpcd -B wlp2s0 4207 - $ ping google.com 4208 - PING ...&lt;/code&gt;&lt;/pre&gt; 4209 - &lt;p&gt;Yay! Make those changes perpetual by enabling the service:&lt;/p&gt; 4210 - &lt;pre&gt;&lt;code&gt;------------------------------------------------------ 4211 - # Add these to /etc/wpa_supplicant/wpa_supplicant.conf 4212 - OPTS=&amp;quot;-B&amp;quot; 4213 - WPA_INTERFACE=&amp;quot;wlp2s0&amp;quot; 4214 - ------------------------------------------------------ 4215 - $ sudo ln -s /etc/sv/wpa_supplicant /var/service/ 4216 - $ sudo ln -s /etc/sv/dhcpcd /var/service/ 4217 - $ sudo sv restart wpa_supplicant 4218 - $ sudo sv restart dhcpcd&lt;/code&gt;&lt;/pre&gt;</description> 4219 - <link>https://oppi.li/posts/WPA_woes/</link> 4220 - <pubDate>Sat, 12 Oct 2019 16:23:00 +0000</pubDate> 4221 - <guid>https://oppi.li/posts/WPA_woes/</guid> 4222 - </item> 4223 - <item> 4224 - <title>Bye Bye BDFs</title> 4225 - <description>&lt;p&gt;Glyph Bitmap Distribution Format is no more, as the creators of &lt;a 4226 - href="https://pango.org"&gt;Pango&lt;/a&gt;, one of the most widely used text 4227 - rendering libraries, &lt;a 4228 - href="https://blogs.gnome.org/mclasen/2019/05/25/pango-future-directions/"&gt;announced&lt;/a&gt; 4229 - their plans for Pango 1.44.&lt;/p&gt; 4230 - &lt;p&gt;Until recently, Pango used FreeType to draw fonts. They will be 4231 - moving over to &lt;a href="https://harfbuzz.org"&gt;Harfbuzz&lt;/a&gt;, an evolution 4232 - of FreeType.&lt;/p&gt; 4233 - &lt;p&gt;&lt;em&gt;Why?&lt;/em&gt;&lt;/p&gt; 4234 - &lt;p&gt;In short, FreeType was hard to work with. It required complex logic, 4235 - and provided no advantage over Harfbuzz (other than being able to fetch 4236 - opentype metrics with ease).&lt;/p&gt; 4237 - &lt;p&gt;Upgrading to Pango v1.44 will break your GTK applications (if you use 4238 - a &lt;code&gt;bdf&lt;/code&gt;/&lt;code&gt;pcf&lt;/code&gt; bitmap font). Harfbuzz &lt;em&gt;does&lt;/em&gt; 4239 - support bitmap-only OpenType fonts, &lt;code&gt;otb&lt;/code&gt;s. Convert your 4240 - existing fonts over to &lt;code&gt;otb&lt;/code&gt;s using &lt;a 4241 - href="https://fontforge.github.io"&gt;FontForge&lt;/a&gt;. It is to be noted that 4242 - applications such as &lt;code&gt;xterm&lt;/code&gt; and &lt;code&gt;rxvt&lt;/code&gt; use 4243 - &lt;code&gt;xft&lt;/code&gt; (X FreeType) to render fonts, and will remain 4244 - unaffected by the update.&lt;/p&gt; 4245 - &lt;p&gt;Both &lt;a 4246 - href="https://github.com/nerdypepper/scientifica"&gt;scientifica&lt;/a&gt; and &lt;a 4247 - href="https://github.com/nerdypepper/curie"&gt;curie&lt;/a&gt; will soon ship 4248 - with bitmap-only OpenType font formats.&lt;/p&gt;</description> 4249 - <link>https://oppi.li/posts/bye_bye_BDFs/</link> 4250 - <pubDate>Wed, 07 Aug 2019 17:26:00 +0000</pubDate> 4251 - <guid>https://oppi.li/posts/bye_bye_BDFs/</guid> 4252 - </item> 4253 - <item> 4254 - <title>Onivim Sucks</title> 4255 - <description>&lt;p&gt;&lt;a href="https://v2.onivim.io"&gt;Onivim&lt;/a&gt; is a ‘modern modal editor’, 4256 - combining fancy interface and language features with vim-style modal 4257 - editing. What’s wrong you ask?&lt;/p&gt; 4258 - &lt;p&gt;Apart from &lt;a href="https://github.com/onivim/oni2/issues/550"&gt;buggy 4259 - syntax highlighting&lt;/a&gt;, &lt;a 4260 - href="https://github.com/onivim/oni2/issues/519"&gt;broken scrolling&lt;/a&gt; 4261 - and &lt;a 4262 - href="https://github.com/onivim/oni2/issues?q=is%3Aissue+label%3A%22daily+editor+blocker%22+is%3Aopen"&gt;others&lt;/a&gt;, 4263 - Onivim is &lt;strong&gt;proprietary&lt;/strong&gt; software. It is licensed under a 4264 - commercial &lt;a 4265 - href="https://github.com/onivim/oni1/blob/master/Outrun-Labs-EULA-v1.1.md"&gt;end 4266 - user agreement license&lt;/a&gt;, which prohibits redistribution in both 4267 - object code and source code formats.&lt;/p&gt; 4268 - &lt;p&gt;Onivim’s core editor logic (bits that belong to vim), have been 4269 - separated from the interface, into &lt;a 4270 - href="https://github.com/onivim/libvim"&gt;libvim&lt;/a&gt;. libvim is licensed 4271 - under MIT, which means, this ‘extension’ of vim is perfectly in 4272 - adherence to &lt;a 4273 - href="http://vimdoc.sourceforge.net/htmldoc/uganda.html#license"&gt;vim’s 4274 - license text&lt;/a&gt;! Outrun Labs are exploiting this loophole (distributing 4275 - vim as a library) to commercialize Onivim.&lt;/p&gt; 4276 - &lt;p&gt;Onivim’s source code is available on &lt;a 4277 - href="https://github.com/onivim/oni2"&gt;GitHub&lt;/a&gt;. They do mention that 4278 - the source code trickles down to the &lt;a 4279 - href="https://github.com/onivim/oni2-mit"&gt;oni2-mit&lt;/a&gt; repository, which 4280 - (not yet) contains MIT-licensed code, &lt;strong&gt;18 months&lt;/strong&gt; after 4281 - each commit to the original repository.&lt;/p&gt; 4282 - &lt;p&gt;Want to contribute to Onivim? Don’t. They make a profit out of your 4283 - contributions. Currently, Onivim is priced at $19.99, ‘pre-alpha’ 4284 - pricing which is 80% off the final price! If you are on the lookout for 4285 - an editor, I would suggest using &lt;a href="https://vim.org"&gt;Vim&lt;/a&gt;, 4286 - charity ware that actually works, and costs $100 lesser.&lt;/p&gt;</description> 4287 - <link>https://oppi.li/posts/onivim_sucks/</link> 4288 - <pubDate>Fri, 02 Aug 2019 16:32:00 +0000</pubDate> 4289 - <guid>https://oppi.li/posts/onivim_sucks/</guid> 4290 - </item> 4291 - <item> 4292 - <title>Bash Harder With Vim</title> 4293 - <description>&lt;p&gt;Bash is tricky, don’t let your editor get in your way. Here’s a 4294 - couple of neat additions you could make to your &lt;code&gt;vimrc&lt;/code&gt; for a 4295 - better shell programming experience.&lt;/p&gt; 4296 - &lt;h3 id="man-pages-inside-vim"&gt;Man pages inside vim&lt;/h3&gt; 4297 - &lt;p&gt;Source this script to get started:&lt;/p&gt; 4298 - &lt;pre&gt;&lt;code&gt;runtime ftplugin/man.vim&lt;/code&gt;&lt;/pre&gt; 4299 - &lt;p&gt;Now, you can open manpages inside vim with &lt;code&gt;:Man&lt;/code&gt;! It adds 4300 - nicer syntax highlighting and the ability to jump around with 4301 - &lt;code&gt;Ctrl-]&lt;/code&gt; and &lt;code&gt;Ctrl-T&lt;/code&gt;.&lt;/p&gt; 4302 - &lt;p&gt;By default, the manpage is opened in a horizontal split, I prefer 4303 - using a new tab:&lt;/p&gt; 4304 - &lt;pre&gt;&lt;code&gt;let g:ft_man_open_mode = &amp;#39;tab&amp;#39;&lt;/code&gt;&lt;/pre&gt; 4305 - &lt;h3 id="scratchpad-to-test-your-commands"&gt;Scratchpad to test your 4306 - commands&lt;/h3&gt; 4307 - &lt;p&gt;I often test my &lt;code&gt;sed&lt;/code&gt; substitutions, here is a sample from 4308 - the script used to generate this site:&lt;/p&gt; 4309 - &lt;pre&gt;&lt;code&gt;# a substitution to convert snake_case to Title Case With Spaces 4310 - echo &amp;quot;$1&amp;quot; | sed -E -e &amp;quot;s/\..+$//g&amp;quot; -e &amp;quot;s/_(.)/ \u\1/g&amp;quot; -e &amp;quot;s/^(.)/\u\1/g&amp;quot;&lt;/code&gt;&lt;/pre&gt; 4311 - &lt;p&gt;Instead of dropping into a new shell, just test it out directly from 4312 - vim!&lt;/p&gt; 4313 - &lt;ul&gt; 4314 - &lt;li&gt;Yank the line into a register:&lt;/li&gt; 4315 - &lt;/ul&gt; 4316 - &lt;pre&gt;&lt;code&gt;yy&lt;/code&gt;&lt;/pre&gt; 4317 - &lt;ul&gt; 4318 - &lt;li&gt;Paste it into the command-line window:&lt;/li&gt; 4319 - &lt;/ul&gt; 4320 - &lt;pre&gt;&lt;code&gt;q:p&lt;/code&gt;&lt;/pre&gt; 4321 - &lt;ul&gt; 4322 - &lt;li&gt;Make edits as required:&lt;/li&gt; 4323 - &lt;/ul&gt; 4324 - &lt;pre&gt;&lt;code&gt;syntax off # previously run commands 4325 - edit index.html # in a buffer! 4326 - w | so % 4327 - !echo &amp;quot;new_post.md&amp;quot; | sed -E -e &amp;quot;s/\..+$//g&amp;quot; --snip-- 4328 - ^--- note the use of &amp;#39;!&amp;#39;&lt;/code&gt;&lt;/pre&gt; 4329 - &lt;ul&gt; 4330 - &lt;li&gt;Hit enter with the cursor on the line containing your command!&lt;/li&gt; 4331 - &lt;/ul&gt; 4332 - &lt;pre&gt;&lt;code&gt;$ vim 4333 - New Post # output 4334 - Press ENTER or type command to continue&lt;/code&gt;&lt;/pre&gt;</description> 4335 - <link>https://oppi.li/posts/bash_harder_with_vim/</link> 4336 - <pubDate>Tue, 30 Jul 2019 18:30:00 +0000</pubDate> 4337 - <guid>https://oppi.li/posts/bash_harder_with_vim/</guid> 4338 - </item> 4339 - <item> 4340 - <title>Hold Position!</title> 4341 - <description>&lt;p&gt;Often times, when I run a vim command that makes “big” changes to a 4342 - file (a macro or a &lt;code&gt;:vimgrep&lt;/code&gt; command) I lose my original 4343 - position and feel disoriented.&lt;/p&gt; 4344 - &lt;p&gt;&lt;em&gt;Save position with &lt;code&gt;winsaveview()&lt;/code&gt;!&lt;/em&gt;&lt;/p&gt; 4345 - &lt;p&gt;The &lt;code&gt;winsaveview()&lt;/code&gt; command returns a 4346 - &lt;code&gt;Dictionary&lt;/code&gt; that contains information about the view of the 4347 - current window. This includes the cursor line number, cursor coloumn, 4348 - the top most line in the window and a couple of other values, none of 4349 - which concern us.&lt;/p&gt; 4350 - &lt;p&gt;Before running our command (one that jumps around the buffer, a lot), 4351 - we save our view, and restore it once its done, with 4352 - &lt;code&gt;winrestview&lt;/code&gt;.&lt;/p&gt; 4353 - &lt;pre&gt;&lt;code&gt;let view = winsaveview() 4354 - s/\s\+$//gc &amp;quot; find and (confirm) replace trailing blanks 4355 - winrestview(view) &amp;quot; restore our original view!&lt;/code&gt;&lt;/pre&gt; 4356 - &lt;p&gt;It might seem a little overkill in the above example, just use `` 4357 - (double backticks) instead, but it comes in handy when you run your file 4358 - through heavier filtering.&lt;/p&gt;</description> 4359 - <link>https://oppi.li/posts/hold_position!/</link> 4360 - <pubDate>Tue, 30 Jul 2019 14:45:00 +0000</pubDate> 4361 - <guid>https://oppi.li/posts/hold_position!/</guid> 4362 - </item> 4363 - <item> 4364 - <title>Get Better At Yanking And Putting In Vim</title> 4365 - <description>&lt;p&gt;a couple of nifty tricks to help you copy-paste better:&lt;/p&gt; 4366 - &lt;ol type="1"&gt; 4367 - &lt;li&gt;&lt;p&gt;reselecting previously selected text (i use this to fix botched 4368 - selections):&lt;/p&gt; 4369 - &lt;pre&gt;&lt;code&gt;gv &amp;quot; :h gv for more 4370 - &amp;quot; you can use `o` in visual mode to go to the `Other` end of the selection 4371 - &amp;quot; use a motion to fix the selection&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt; 4372 - &lt;li&gt;&lt;p&gt;reselecting previously yanked text:&lt;/p&gt; 4373 - &lt;pre&gt;&lt;code&gt;`[v`] 4374 - `[ &amp;quot; marks the beginning of the previously yanked text :h `[ 4375 - `] &amp;quot; marks the end :h `] 4376 - v &amp;quot; visual select everything in between 4377 - 4378 - nnoremap gb `[v`] &amp;quot; &amp;quot;a quick map to perform the above&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt; 4379 - &lt;li&gt;&lt;p&gt;pasting and indenting text (in one go):&lt;/p&gt; 4380 - &lt;pre&gt;&lt;code&gt;]p &amp;quot; put (p) and adjust indent to current line 4381 - ]P &amp;quot; put the text before the cursor (P) and adjust indent to current line&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt; 4382 - &lt;/ol&gt;</description> 4383 - <link>https://oppi.li/posts/get_better_at_yanking_and_putting_in_vim/</link> 4384 - <pubDate>Tue, 30 Jul 2019 08:43:00 +0000</pubDate> 4385 - <guid>https://oppi.li/posts/get_better_at_yanking_and_putting_in_vim/</guid> 4386 - </item> 4387 - 4388 - 4389 - </channel> 4390 - </rss>
-115
docs/posts/OSC-52/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="OSC-52"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/OSC-52"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>OSC-52 · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">OSC-52</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/OSC-52.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 27/11 — 2024 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 26.37 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 1.9 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - OSC-52 43 - </h1> 44 - <div class="post-text"> 45 - <p>I use <code>ssh</code> a lot. Copying text from the remote machine to 46 - the host machine always sucked. But OSC-52 makes that easy.</p> 47 - <p>OSC-52 is an ANSI escape sequence to write text to the terminal 48 - emulator. The terminal emulator, if it understands what is going on, 49 - will in turn write this text to the system clipboard.</p> 50 - <p>What this means is some <code>printf</code> magic can send text to 51 - your clipboard. I store this one-liner in a script called 52 - <code>oclip</code>:</p> 53 - <div class="sourceCode" id="cb1"><pre 54 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="bu">printf</span> <span class="st">&quot;\033]52;c;%s\007&quot;</span> <span class="st">&quot;</span><span class="va">$(</span><span class="fu">base64</span> <span class="op">&lt;&amp;</span><span class="dv">0</span><span class="va">)</span><span class="st">&quot;</span></span></code></pre></div> 55 - <p>and I run it with:</p> 56 - <div class="sourceCode" id="cb2"><pre 57 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">remote</span> $ cat some_file.txt <span class="kw">|</span> <span class="ex">oclip</span></span> 58 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a></span> 59 - <span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="co"># some_file.txt&#39;s contents are now the host&#39;s clipboard</span></span></code></pre></div> 60 - <h3 id="the-catch">The catch</h3> 61 - <p>Your terminal emulator must support OSC-52, <code>alacritty</code> 62 - and <code>termux</code> seem to support this out of the box. In 63 - <code>st</code>, OSC-52 works with this change to 64 - <code>config.h</code>:</p> 65 - <pre><code>int allowwindowops = 1;</code></pre> 66 - <p>If you are using <code>tmux</code>, you need to flip this switch 67 - on:</p> 68 - <pre><code>set -s set-clipboard on</code></pre> 69 - <p>If you are inside <code>nvim</code>, it may work as expected as long 70 - as <code>$SSH_TTY</code> is set. I sometimes physically start a session, 71 - and <code>ssh</code> into the same session later from another machine, 72 - and <code>$SSH_TTY</code> remains unset, so I force OSC-52 in 73 - <code>nvim</code> at all times (see <a 74 - href="https://neovim.io/doc/user/provider.html#clipboard-osc52">nvimdoc</a>):</p> 75 - <div class="sourceCode" id="cb5"><pre 76 - class="sourceCode lua"><code class="sourceCode lua"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="va">vim</span><span class="op">.</span><span class="va">g</span><span class="op">.</span><span class="va">clipboard</span> <span class="op">=</span> <span class="op">{</span></span> 77 - <span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a> <span class="va">name</span> <span class="op">=</span> <span class="st">&#39;OSC 52&#39;</span><span class="op">,</span></span> 78 - <span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a> <span class="va">copy</span> <span class="op">=</span> <span class="op">{</span></span> 79 - <span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a> <span class="op">[</span><span class="st">&#39;+&#39;</span><span class="op">]</span> <span class="op">=</span> <span class="fu">require</span><span class="op">(</span><span class="st">&#39;vim.ui.clipboard.osc52&#39;</span><span class="op">).</span>copy<span class="op">(</span><span class="st">&#39;+&#39;</span><span class="op">),</span></span> 80 - <span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a> <span class="op">[</span><span class="st">&#39;*&#39;</span><span class="op">]</span> <span class="op">=</span> <span class="fu">require</span><span class="op">(</span><span class="st">&#39;vim.ui.clipboard.osc52&#39;</span><span class="op">).</span>copy<span class="op">(</span><span class="st">&#39;*&#39;</span><span class="op">),</span></span> 81 - <span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a> <span class="op">},</span></span> 82 - <span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a> <span class="va">paste</span> <span class="op">=</span> <span class="op">{</span></span> 83 - <span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a> <span class="op">[</span><span class="st">&#39;+&#39;</span><span class="op">]</span> <span class="op">=</span> <span class="fu">require</span><span class="op">(</span><span class="st">&#39;vim.ui.clipboard.osc52&#39;</span><span class="op">).</span>paste<span class="op">(</span><span class="st">&#39;+&#39;</span><span class="op">),</span></span> 84 - <span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a> <span class="op">[</span><span class="st">&#39;*&#39;</span><span class="op">]</span> <span class="op">=</span> <span class="fu">require</span><span class="op">(</span><span class="st">&#39;vim.ui.clipboard.osc52&#39;</span><span class="op">).</span>paste<span class="op">(</span><span class="st">&#39;*&#39;</span><span class="op">),</span></span> 85 - <span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a> <span class="op">},</span></span> 86 - <span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 87 - <p>If you are inside <code>nvim</code> inside <code>tmux</code> inside 88 - an <code>ssh</code> session inside <code>st</code>, you neeed all of the 89 - above tweaks. <code>nvim</code> will pass the contents around to 90 - <code>tmux</code>, which in turn will pass the contents to 91 - <code>st</code>, which should pass it to your system clipboard.</p> 92 - 93 - </div> 94 - 95 - <div class="intro"> 96 - Hi. 97 - <div class="hot-links"> 98 - <a href="/index.xml" class="feed-button">Subscribe</a> 99 - </div> 100 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 101 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 102 - <p>Reach out at oppili@libera.chat.</p> 103 - </div> 104 - 105 - <a href="/" class="post-end-link">Home</a> 106 - <span>/</span> 107 - <a href="/posts" class="post-end-link">Posts</a> 108 - <span>/</span> 109 - <a class="post-end-link">OSC-52</a> 110 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/OSC-52.md 111 - ">View Raw</a> 112 - </div> 113 - </div> 114 - </body> 115 - </html>
-349
docs/posts/SDL2_devlog/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="SDL2 Devlog"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/SDL2_devlog"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>SDL2 Devlog · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">SDL2 Devlog</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/SDL2_devlog.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 11/04 — 2021 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 124.28 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 10.0 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - SDL2 Devlog 43 - </h1> 44 - <div class="post-text"> 45 - <p>I have been working on an editor for the <a 46 - href="https://git.peppe.rs/graphics/obi/about">One Bit Image</a> file 47 - format in Rust and SDL2. This entry in my blog follows my progress on 48 - the editor. The days are listed in reverse chronological order, begin 49 - from the bottom, if this is your first time on this page.</p> 50 - <h3 id="day-20">Day 20</h3> 51 - <p>More <code>lisp</code> stuff! I added a new brush, for rectangular 52 - selections. While selection doesn’t do much on its own, the selected 53 - area can be passed onto a <code>lisp</code> procedure, for example, a 54 - procedure to draw horizontal black and white lines:</p> 55 - <figure> 56 - <video src="https://u.peppe.rs/frU.mp4" controls=""><a 57 - href="https://u.peppe.rs/frU.mp4">Day 20</a></video> 58 - <figcaption aria-hidden="true">Day 20</figcaption> 59 - </figure> 60 - <h3 id="day-19">Day 19</h3> 61 - <p>Attempted <a href="https://peppe.rs/art/conduit.png">some isometric 62 - art</a> within the editor. The angles displayed alongside the line brush 63 - are handly, however, having only a rectangular grid did not help. I 64 - implemented an isometric grid today. Isometric grids in pixel art differ 65 - in that the tangent of the isometric angle is exactly 0.5! For every 66 - pixel down, you go exactly two pixels sideways. The math works out 67 - really well in the drawing procedures too, dealing with floating points 68 - is a pain.</p> 69 - <figure> 70 - <img src="https://u.peppe.rs/1Kb.png" alt="Day 19" /> 71 - <figcaption aria-hidden="true">Day 19</figcaption> 72 - </figure> 73 - <h3 id="day-18">Day 18</h3> 74 - <p>I added basic support for guides, they can be added and activated 75 - from the <code>lisp</code> REPL. Another long standing improvement I 76 - wanted to make was reworking the pixmap drawing procedure. The old 77 - procedure draws a square for each pixel in the pixmap, coloured 78 - according to its value in the pixmap. Naturally, this means, for an 79 - <strong>NxN</strong> pixmap, there are <strong>N²</strong> calls to SDL! 80 - I reworked this procedure to compress each line of the pixmap using RLE 81 - (run length encoding), and call out to SDL for each run in the line. 82 - This drastically improved drawing speeds on larger grids. The following 83 - is a comparison between the two procedures, the leftmost picture is the 84 - rendered image, the middle picture is the optimized drawing procedure 85 - (draws each run instead of pixel), and the right most picture is the 86 - primitive drawing procedure (draws each pixel):</p> 87 - <figure> 88 - <img src="https://u.peppe.rs/U4B.png" alt="Day 18" /> 89 - <figcaption aria-hidden="true">Day 18</figcaption> 90 - </figure> 91 - <h3 id="day-17">Day 17</h3> 92 - <p>I decided to give the text-only statusline a touch up, by adding a 93 - active color and dither level preview. Aligning the “widget” to the 94 - right of statusline involved a lot more than I thought, so I created a 95 - ghetto CSS-like rectangle placement system to position containers inside 96 - containers:</p> 97 - <div class="sourceCode" id="cb1"><pre 98 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co">// roughly something like this</span></span> 99 - <span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> statusline <span class="op">=</span> </span> 100 - <span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="pp">Container::</span>new(<span class="pp">Offset::</span>Left(<span class="dv">0</span>)<span class="op">,</span> <span class="pp">Offset::</span>Bottom(<span class="dv">40</span>))</span> 101 - <span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>width(<span class="pp">Size::</span>Max)</span> 102 - <span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>height(<span class="pp">Size::</span>Absolute(<span class="dv">20</span>))<span class="op">;</span></span> 103 - <span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> </span> 104 - <span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="kw">mut</span> primary <span class="op">=</span> <span class="pp">Container::</span>uninit()</span> 105 - <span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>width(<span class="pp">Size::</span>Absolute(<span class="dv">16</span>))</span> 106 - <span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>height(<span class="pp">Size::</span>Absolute(<span class="dv">16</span>))<span class="op">;</span></span> 107 - <span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a> </span> 108 - <span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a>container<span class="op">.</span>place(</span> 109 - <span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a> <span class="op">&amp;</span><span class="kw">mut</span> padding_box<span class="op">,</span></span> 110 - <span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a> <span class="pp">HorAlign::</span>Right<span class="op">,</span></span> 111 - <span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a> <span class="pp">VertAlign::</span>Center</span> 112 - <span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a>)<span class="op">;</span></span></code></pre></div> 113 - <p>The result (brush preview on the bottom right):</p> 114 - <figure> 115 - <video src="https://u.peppe.rs/OtU.mp4" controls=""><a 116 - href="https://u.peppe.rs/OtU.mp4">Day 17</a></video> 117 - <figcaption aria-hidden="true">Day 17</figcaption> 118 - </figure> 119 - <h3 id="day-16">Day 16</h3> 120 - <p>The embedded lisp is coming along nicely, users can load a custom 121 - <code>rc.lisp</code>, which is evaluated on startup. To disable to grid 122 - on start, for example:</p> 123 - <div class="sourceCode" id="cb2"><pre 124 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">;;; rc.lisp</span></span> 125 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a>(toggle-grid)</span></code></pre></div> 126 - <p>Some aliases to switch between brushes:</p> 127 - <div class="sourceCode" id="cb3"><pre 128 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co">;;; rc.lisp</span></span> 129 - <span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>(<span class="ex">define</span><span class="fu"> </span>(brush kind)</span> 130 - <span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> (<span class="kw">cond</span></span> 131 - <span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> ((<span class="kw">eq?</span> kind &#39;f) (brush-fill))</span> 132 - <span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> ((<span class="kw">eq?</span> kind &#39;c) (brush-circle))</span> 133 - <span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a> ((<span class="kw">eq?</span> kind &#39;l) (brush-line))</span> 134 - <span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a> ((<span class="kw">eq?</span> kind &#39;l+) (brush-line-extend))</span> 135 - <span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a> (<span class="kw">else</span> (brush-circle))))</span></code></pre></div> 136 - <p>The following script draws a straight line along a given axis, at a 137 - given distance from the canvas boundary:</p> 138 - <figure> 139 - <video src="https://u.peppe.rs/b3i.mp4" controls=""><a 140 - href="https://u.peppe.rs/b3i.mp4">Day 16</a></video> 141 - <figcaption aria-hidden="true">Day 16</figcaption> 142 - </figure> 143 - <h3 id="day-15">Day 15</h3> 144 - <p>I began writing a standard library for the lisp, in lisp. It includes 145 - basic list operations: <code>car</code>, <code>cdr</code>, 146 - <code>null?</code>, <code>list</code>, higher order functions: 147 - <code>map</code>, <code>filter</code>, <code>fold</code>:</p> 148 - <div class="sourceCode" id="cb4"><pre 149 - class="sourceCode lisp"><code class="sourceCode commonlisp"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a>(define (member? item ls)</span> 150 - <span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> (fold <span class="dv">#f</span></span> 151 - <span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> (<span class="kw">lambda</span> (acc x) (<span class="kw">or</span> acc (eq? item x)))</span> 152 - <span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a> ls))</span></code></pre></div> 153 - <h3 id="day-14">Day 14</h3> 154 - <p>I attempted a <a href="https://peppe.rs/art/ramen_noodles.png">small 155 - art piece</a> using the editor, while it was largely usable, I felt a 156 - certain lack of feedback. The brushes just didn’t relay as much info as 157 - I’d have liked, for example, the approximate points of the line or the 158 - angle made by the line against the x-axis. Unfortunately, the existing 159 - infrastructure around brushes and line drawing didn’t easily allow for 160 - this either. I went ahead and reimplemented brushes, and added a new 161 - flood fill brush too:</p> 162 - <figure> 163 - <video src="https://u.peppe.rs/8q.mp4" controls=""><a 164 - href="https://u.peppe.rs/8q.mp4">Day 14</a></video> 165 - <figcaption aria-hidden="true">Day 14</figcaption> 166 - </figure> 167 - <h3 id="day-13">Day 13</h3> 168 - <p>I added a few more forms to the <code>lisp</code> evaluator. It 169 - handles recursion, definitions, variable mutation and more. The prelude 170 - contains 20 subroutines so far, including comparision and logic 171 - operators. The REPL interface on the SDL side requires some UX tweaks; 172 - environment based completion, readline motions sound doable.</p> 173 - <figure> 174 - <video src="https://u.peppe.rs/u3.mp4" controls=""><a 175 - href="https://u.peppe.rs/u3.mp4">Day 13</a></video> 176 - <figcaption aria-hidden="true">Day 13</figcaption> 177 - </figure> 178 - <h3 id="day-12">Day 12</h3> 179 - <p>I lifted most of <a 180 - href="https://github.com/murarth/ketos">murarth/ketos</a> into the 181 - editor. <code>ketos</code>’s implementation of <code>lisp</code> is too 182 - vast for my use case. For example, the editor does not need data types 183 - to handle raw strings or byte strings. I have got a basic evaluator 184 - running inside the SDL2 context (notice the <code>lisp</code> REPL at 185 - the bottom of the window). Over the following days, I intend to create a 186 - set of prelude functions to manipulate the pixmap. Users can implement 187 - their own brushes, dithering patterns, keybinds and more 188 - (hopefully).</p> 189 - <figure> 190 - <video src="https://u.peppe.rs/y0.mp4" controls=""><a 191 - href="https://u.peppe.rs/y0.mp4">Day 12</a></video> 192 - <figcaption aria-hidden="true">Day 12</figcaption> 193 - </figure> 194 - <h3 id="day-11">Day 11</h3> 195 - <p>I intend to supplement the editor with scripting language and an 196 - inbuilt REPL for the same. I began by implementing a text box widget 197 - from scratch, with history and readline like editing:</p> 198 - <figure> 199 - <video src="https://u.peppe.rs/Mh.mp4" controls=""><a 200 - href="https://u.peppe.rs/Mh.mp4">Day 11</a></video> 201 - <figcaption aria-hidden="true">Day 11</figcaption> 202 - </figure> 203 - <h3 id="day-10">Day 10</h3> 204 - <p>I started reading up on dithering methods and half-toning, I wanted 205 - to create a dithering brush that would automatically produce popular 206 - dithering patterns. The method that caught my eye (and also the one used 207 - most often in pixel art), was Bayer’s ordered dithering. When applied to 208 - a black and white image, each pixel, based on its intensity, is mapped 209 - to a 4x4 grid of pixels. A completely empty (completely black) 4x4 grid 210 - represents zero intensity, and a filled 4x4 grid represents full 211 - intensity. Bayer’s ordered dithering can produce 15 steps of intensity 212 - between zero and full (by switching on exactly 1 pixel more at each 213 - level), thus, being able to draw 17 “shades” from white to black. 214 - Creating a dithering brush from here was fairly trivial. Our pixmap is 215 - supposed to represent the final dithered image, it must be divided into 216 - 4x4 grids. Each grid is colored based on the intensity of the brush 217 - passing over it:</p> 218 - <figure> 219 - <img src="https://u.peppe.rs/Mn.png" alt="Day 10" /> 220 - <figcaption aria-hidden="true">Day 10</figcaption> 221 - </figure> 222 - <h3 id="day-9">Day 9</h3> 223 - <p>I started working towards an interface. I like the idea of a largely 224 - read-only HUD, i. e., an interface that simply describes the state of 225 - the application. Changes to this state are initiated via keybinds or 226 - text commands. I am proud of the symmetry indicator; <code>-</code> for 227 - horizontal symmetry, <code>|</code> for vertical symmetry, 228 - <code>+</code> for radial symmetry.</p> 229 - <figure> 230 - <img src="https://u.peppe.rs/hx.png" alt="Day 9" /> 231 - <figcaption aria-hidden="true">Day 9</figcaption> 232 - </figure> 233 - <h3 id="day-8">Day 8</h3> 234 - <p>One of my favourite features of GIMP was symmetric editing. I added 235 - some coordinate geometry primitives to my pixmap abstraction, allowing 236 - for mirroring and reflecting figures about lines or points. The result 237 - was an ergonomic function that applies symmetry to any painting 238 - operation, (undo/redo works as expected):</p> 239 - <div class="sourceCode" id="cb5"><pre 240 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> line <span class="op">=</span> <span class="kw">self</span><span class="op">.</span>pixmap<span class="op">.</span>get_line(start<span class="op">,</span> end)<span class="op">;</span></span> 241 - <span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> sym_line <span class="op">=</span> <span class="kw">self</span><span class="op">.</span>symmetry<span class="op">.</span>apply(<span class="op">&amp;</span>line)<span class="op">;</span></span> 242 - <span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> point on line<span class="op">.</span>extend(sym_line) <span class="op">{</span></span> 243 - <span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a> <span class="co">// draw to window</span></span> 244 - <span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 245 - <figure> 246 - <video src="https://u.peppe.rs/B1.mp4" controls=""><a 247 - href="https://u.peppe.rs/B1.mp4">Day 8</a></video> 248 - <figcaption aria-hidden="true">Day 8</figcaption> 249 - </figure> 250 - <h3 id="day-7">Day 7</h3> 251 - <p>Bresenham saves the day again! This time, I implemented his line 252 - drawing algorithm, to, well, draw lines. Each point on the line is then 253 - “buffed” based on the active brush size. Today’s changes fit in very 254 - well with the undo system and the brush size feature. Creating the right 255 - abstractions, one at a time :)</p> 256 - <figure> 257 - <video src="https://u.peppe.rs/xt.mp4" controls=""><a 258 - href="https://u.peppe.rs/xt.mp4">Day 7</a></video> 259 - <figcaption aria-hidden="true">Day 7</figcaption> 260 - </figure> 261 - <h3 id="day-6">Day 6</h3> 262 - <p>I extended Bresenham’s algorithm to draw not just circle outlines, 263 - but also generate their fills. Unlike Bresenham’s algorithm, this 264 - variant generates points for two quadrants at once, these points are 265 - mirrored over the dividing axis to generate the other two quadrants.</p> 266 - <figure> 267 - <img src="https://u.peppe.rs/f3.png" alt="Day 6" /> 268 - <figcaption aria-hidden="true">Day 6</figcaption> 269 - </figure> 270 - <h3 id="day-5">Day 5</h3> 271 - <p>I discovered and implemented Bresenham’s algorithm for efficient 272 - circle drawing. The algorithm allowed for sized circular brushes, 273 - something I really liked from GIMP. Very convenient that the Wikipedia 274 - page for Bresenham’s algorithm also includes a section about optimizing 275 - for integer based arithmetic. I managed to abstract out another giant 276 - component of the application, the pixmap. Any image is just a grid of 277 - pixels (a pixmap), where the pixel’s value is decided by the application 278 - (1-bit in my case). I could potentially extend the application to a 279 - 24-bit image editor!</p> 280 - <figure> 281 - <video src="https://u.peppe.rs/Kh.mp4" controls=""><a 282 - href="https://u.peppe.rs/Kh.mp4">Day 5</a></video> 283 - <figcaption aria-hidden="true">Day 5</figcaption> 284 - </figure> 285 - <h3 id="day-4">Day 4</h3> 286 - <p>I created a generic “undo stack” data structure that allows for 287 - infinite “undos” and “redos”. Every modification operation to the grid 288 - is persisted to the application state. A couple of keybinds allow the 289 - user to revert and re-apply these operations! I expect abstracting this 290 - component will come in handy down the line.</p> 291 - <figure> 292 - <video src="https://u.peppe.rs/w5.mp4" controls=""><a 293 - href="https://u.peppe.rs/w5.mp4">Day 4</a></video> 294 - <figcaption aria-hidden="true">Day 4</figcaption> 295 - </figure> 296 - <h3 id="day-3">Day 3</h3> 297 - <p>I implemented the bare minimum required to call the program an 298 - “editor”. The application displays a grid, tracks mouse events, paints 299 - white to the canvas on left click, and black to the canvas on right 300 - click. I created a make-shift MVC architecture à la Elm in Rust.</p> 301 - <figure> 302 - <video src="https://u.peppe.rs/GF.mp4" controls=""><a 303 - href="https://u.peppe.rs/GF.mp4">Day 3</a></video> 304 - <figcaption aria-hidden="true">Day 3</figcaption> 305 - </figure> 306 - <h3 id="day-2">Day 2</h3> 307 - <p>I started figuring out event handling today. Implemented a couple of 308 - keybinds to zoom in/out of the drawing area. Conversions of SDL2 309 - coordinates (measured in signed 32 bit integers) to my internal “drawing 310 - area” coordinates (measured in unsigned 32 bit integers) is very 311 - annoying. Hopefully the unchecked conversions won’t haunt me later.</p> 312 - <figure> 313 - <video src="https://u.peppe.rs/L4.mp4" controls=""><a 314 - href="https://u.peppe.rs/L4.mp4">Day 2</a></video> 315 - <figcaption aria-hidden="true">Day 2</figcaption> 316 - </figure> 317 - <h3 id="day-1">Day 1</h3> 318 - <p>Getting started with Rust and SDL2 is very straightforward. The 319 - <code>rust-sdl2</code> library contains some detailed examples that 320 - allowed me to get all the way to drawing a grid from a 321 - <code>Vec&lt;bool&gt;</code>:</p> 322 - <figure> 323 - <img src="https://u.peppe.rs/Ma.png" alt="Day 1" /> 324 - <figcaption aria-hidden="true">Day 1</figcaption> 325 - </figure> 326 - 327 - </div> 328 - 329 - <div class="intro"> 330 - Hi. 331 - <div class="hot-links"> 332 - <a href="/index.xml" class="feed-button">Subscribe</a> 333 - </div> 334 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 335 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 336 - <p>Reach out at oppili@libera.chat.</p> 337 - </div> 338 - 339 - <a href="/" class="post-end-link">Home</a> 340 - <span>/</span> 341 - <a href="/posts" class="post-end-link">Posts</a> 342 - <span>/</span> 343 - <a class="post-end-link">SDL2 Devlog</a> 344 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/SDL2_devlog.md 345 - ">View Raw</a> 346 - </div> 347 - </div> 348 - </body> 349 - </html>
-97
docs/posts/WPA_woes/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="WPA Woes"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/WPA_woes"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>WPA Woes · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">WPA Woes</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/WPA_woes.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 12/10 — 2019 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 18.38 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 1.1 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - WPA Woes 43 - </h1> 44 - <div class="post-text"> 45 - <p>I finally got around to installing Void GNU/Linux on my main 46 - computer. Rolling release, non-systemd, need I say more?</p> 47 - <p>As with all GNU/Linux distributions, wireless networks had me in a 48 - fix. If you can see this post, it means I’ve managed to get online. It 49 - turns out, <code>wpa_supplicant</code> was detecting the wrong interface 50 - by default (does it ever select the right one?). Let us fix that:</p> 51 - <pre><code>$ sudo rm -r /var/service/wpa_supplicant 52 - $ sudo killall dhcpcd</code></pre> 53 - <p>What is the right interface though?</p> 54 - <pre><code>$ iw dev 55 - ... 56 - Interface wlp2s0 57 - ...</code></pre> 58 - <p>Aha! Let us run <code>wpa_supplicant</code> on that interface, as a 59 - background process:</p> 60 - <pre><code>$ sudo wpa_supplicant -B -i wlp2s0 -c /etc/wpa_supplicant/wpa_supplicant.conf 61 - $ sudo dhcpcd -B wlp2s0 62 - $ ping google.com 63 - PING ...</code></pre> 64 - <p>Yay! Make those changes perpetual by enabling the service:</p> 65 - <pre><code>------------------------------------------------------ 66 - # Add these to /etc/wpa_supplicant/wpa_supplicant.conf 67 - OPTS=&quot;-B&quot; 68 - WPA_INTERFACE=&quot;wlp2s0&quot; 69 - ------------------------------------------------------ 70 - $ sudo ln -s /etc/sv/wpa_supplicant /var/service/ 71 - $ sudo ln -s /etc/sv/dhcpcd /var/service/ 72 - $ sudo sv restart wpa_supplicant 73 - $ sudo sv restart dhcpcd</code></pre> 74 - 75 - </div> 76 - 77 - <div class="intro"> 78 - Hi. 79 - <div class="hot-links"> 80 - <a href="/index.xml" class="feed-button">Subscribe</a> 81 - </div> 82 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 83 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 84 - <p>Reach out at oppili@libera.chat.</p> 85 - </div> 86 - 87 - <a href="/" class="post-end-link">Home</a> 88 - <span>/</span> 89 - <a href="/posts" class="post-end-link">Posts</a> 90 - <span>/</span> 91 - <a class="post-end-link">WPA Woes</a> 92 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/WPA_woes.md 93 - ">View Raw</a> 94 - </div> 95 - </div> 96 - </body> 97 - </html>
-108
docs/posts/a_reference_counted_afterlife/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="A Reference Counted Afterlife"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/a_reference_counted_afterlife"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>A Reference Counted Afterlife · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">A Reference Counted Afterlife</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/a_reference_counted_afterlife.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 02/08 — 2022 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 19.58 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 1.6 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - A Reference Counted Afterlife 43 - </h1> 44 - <div class="post-text"> 45 - <p>I took interest in the Egyptian rendition of the afterlife 46 - recently.</p> 47 - <h3 id="parts-of-the-soul">Parts of the Soul</h3> 48 - <p>Ancient Egyptians believed that the soul comprised of several 49 - components:</p> 50 - <ul> 51 - <li><em>ren</em></li> 52 - <li><em>ka</em></li> 53 - <li><em>ib</em></li> 54 - <li><em>ba</em></li> 55 - <li><em>sheut</em></li> 56 - </ul> 57 - <p>Egyptians emphasized on preserving the different parts of the soul. 58 - Mummification for example, served to preserve the physical part of the 59 - soul. The other components have their respective preservation 60 - strategies.</p> 61 - <p>Of all of these bits, I find <em>ren</em>, which simply means 62 - <em>name</em>, to be the most interesting. <em>Ba</em>, the human-headed 63 - chicken that represents <em>personality</em>, is a close favourite.</p> 64 - <p><em>Ren</em> is the name given to a person at birth. Egyptians 65 - believed that this portion of the soul would continue to live on for as 66 - long as it was spoken. If you were someone worthy of continued 67 - existence, your name would be inscribed all over the place. If you were 68 - the type to snatch away bread from children, your name would be 69 - condemned from memory, forgotten.</p> 70 - <h3 id="garbage-collection">Garbage-collection</h3> 71 - <p>The concept of <em>ren</em> seems to be perfectly analogous to 72 - reference counted garbage-collection.</p> 73 - <ul> 74 - <li>A name (<em>ren</em>) is assigned to an object (person) on 75 - initialization (at birth)</li> 76 - <li>Names are used to refer to objects</li> 77 - <li>Objects go out of existence when there are no more references to 78 - them</li> 79 - </ul> 80 - <p>The concept of <em>ren</em> seems to model human-memory. The 81 - similarity with garbage-collection is now easily explained, because 82 - garbage-collection models a program’s memory.</p> 83 - <p>Perhaps some cheeky Egyptian has attained immortality by creating a 84 - <em>ren</em>-cycle.</p> 85 - 86 - </div> 87 - 88 - <div class="intro"> 89 - Hi. 90 - <div class="hot-links"> 91 - <a href="/index.xml" class="feed-button">Subscribe</a> 92 - </div> 93 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 94 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 95 - <p>Reach out at oppili@libera.chat.</p> 96 - </div> 97 - 98 - <a href="/" class="post-end-link">Home</a> 99 - <span>/</span> 100 - <a href="/posts" class="post-end-link">Posts</a> 101 - <span>/</span> 102 - <a class="post-end-link">A Reference Counted Afterlife</a> 103 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/a_reference_counted_afterlife.md 104 - ">View Raw</a> 105 - </div> 106 - </div> 107 - </body> 108 - </html>
-794
docs/posts/auto-currying_rust_functions/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Auto-currying Rust Functions"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/auto-currying_rust_functions"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Auto-currying Rust Functions · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Auto-currying Rust Functions</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/auto-currying_rust_functions.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 08/05 — 2020 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 356.44 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 25.1 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Auto-currying Rust Functions 43 - </h1> 44 - <div class="post-text"> 45 - <p>This post contains a gentle introduction to procedural macros in Rust 46 - and a guide to writing a procedural macro to curry Rust functions. The 47 - source code for the entire library can be found <a 48 - href="https://github.com/nerdypepper/cutlass">here</a>. It is also 49 - available on <a 50 - href="https://crates.io/crates/cutlass">crates.io</a>.</p> 51 - <p>The following links might prove to be useful before getting 52 - started:</p> 53 - <ul> 54 - <li><a 55 - href="https://doc.rust-lang.org/reference/procedural-macros.html">Procedural 56 - Macros</a></li> 57 - <li><a href="https://en.wikipedia.org/wiki/Currying">Currying</a></li> 58 - </ul> 59 - <p>Or you can pretend you read them, because I have included a primer 60 - here :)</p> 61 - <h3 id="contents">Contents</h3> 62 - <ol type="1"> 63 - <li><a href="#currying">Currying</a><br /> 64 - </li> 65 - <li><a href="#procedural-macros">Procedural Macros</a><br /> 66 - </li> 67 - <li><a href="#definitions">Definitions</a><br /> 68 - </li> 69 - <li><a href="#refinement">Refinement</a><br /> 70 - </li> 71 - <li><a href="#the-in-betweens">The In-betweens</a><br /> 72 -      5.1 <a href="#dependencies">Dependencies</a><br /> 73 -      5.2 <a href="#the-attribute-macro">The attribute macro</a><br /> 74 -      5.3 <a href="#function-body">Function Body</a><br /> 75 -      5.4 <a href="#function-signature">Function Signature</a><br /> 76 -      5.5 <a href="#getting-it-together">Getting it together</a><br /> 77 - </li> 78 - <li><a href="#debugging-and-testing">Debugging and Testing</a><br /> 79 - </li> 80 - <li><a href="#notes">Notes</a><br /> 81 - </li> 82 - <li><a href="#conclusion">Conclusion</a></li> 83 - </ol> 84 - <h3 id="currying">Currying</h3> 85 - <p>Currying is the process of transformation of a function call like 86 - <code>f(a, b, c)</code> to <code>f(a)(b)(c)</code>. A curried function 87 - returns a concrete value only when it receives all its arguments! If it 88 - does recieve an insufficient amount of arguments, say 1 of 3, it returns 89 - a <em>curried function</em>, that returns after receiving 2 90 - arguments.</p> 91 - <pre><code>curry(f(a, b, c)) = h(a)(b)(c) 92 - 93 - h(x) = g &lt;- curried function that takes upto 2 args (g) 94 - g(y) = k &lt;- curried function that takes upto 1 arg (k) 95 - k(z) = v &lt;- a value (v) 96 - 97 - Keen readers will conclude the following, 98 - h(x)(y)(z) = g(y)(z) = k(z) = v</code></pre> 99 - <p>Mathematically, if <code>f</code> is a function that takes two 100 - arguments <code>x</code> and <code>y</code>, such that 101 - <code>x ϵ X</code>, and <code>y ϵ Y</code> , we write it as:</p> 102 - <pre><code>f: (X × Y) -&gt; Z</code></pre> 103 - <p>where <code>×</code> denotes the Cartesian product of set 104 - <code>X</code> and <code>Y</code>, and curried <code>f</code> (denoted 105 - by <code>h</code> here) is written as:</p> 106 - <pre><code>h: X -&gt; (Y -&gt; Z)</code></pre> 107 - <h3 id="procedural-macros">Procedural Macros</h3> 108 - <p>These are functions that take code as input and spit out modified 109 - code as output. Powerful stuff. Rust has three kinds of proc-macros:</p> 110 - <ul> 111 - <li>Function like macros<br /> 112 - </li> 113 - <li>Derive macros: <code>#[derive(...)]</code>, used to automatically 114 - implement traits for structs/enums<br /> 115 - </li> 116 - <li>and Attribute macros: <code>#[test]</code>, usually slapped onto 117 - functions</li> 118 - </ul> 119 - <p>We will be using Attribute macros to convert a Rust function into a 120 - curried Rust function, which we should be able to call via: 121 - <code>function(arg1)(arg2)</code>.</p> 122 - <h3 id="definitions">Definitions</h3> 123 - <p>Being respectable programmers, we define the input to and the output 124 - from our proc-macro. Here’s a good non-trivial function to start out 125 - with:</p> 126 - <div class="sourceCode" id="cb4"><pre 127 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> add(x<span class="op">:</span> <span class="dt">u32</span><span class="op">,</span> y<span class="op">:</span> <span class="dt">u32</span><span class="op">,</span> z<span class="op">:</span> <span class="dt">u32</span>) <span class="op">-&gt;</span> <span class="dt">u32</span> <span class="op">{</span></span> 128 - <span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> x <span class="op">+</span> y <span class="op">+</span> z<span class="op">;</span></span> 129 - <span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 130 - <p>Hmm, what would our output look like? What should our proc-macro 131 - generate ideally? Well, if we understood currying correctly, we should 132 - accept an argument and return a function that accepts an argument and 133 - returns … you get the point. Something like this should do:</p> 134 - <div class="sourceCode" id="cb5"><pre 135 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> add_curried1(x<span class="op">:</span> <span class="dt">u32</span>) <span class="op">-&gt;</span> <span class="op">?</span> <span class="op">{</span></span> 136 - <span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="kw">fn</span> add_curried2 (y<span class="op">:</span> <span class="dt">u32</span>) <span class="op">-&gt;</span> <span class="op">?</span> <span class="op">{</span></span> 137 - <span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="kw">fn</span> add_curried3 (z<span class="op">:</span> <span class="dt">u32</span>) <span class="op">-&gt;</span> <span class="dt">u32</span> <span class="op">{</span></span> 138 - <span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> x <span class="op">+</span> y <span class="op">+</span> z<span class="op">;</span></span> 139 - <span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 140 - <span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 141 - <span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 142 - <p>A couple of things to note:</p> 143 - <p><strong>Return types</strong><br /> 144 - We have placed <code>?</code>s in place of return types. Let’s try to 145 - fix that. <code>add_curried3</code> returns the ‘value’, so 146 - <code>u32</code> is accurate. <code>add_curried2</code> returns 147 - <code>add_curried3</code>. What is the type of 148 - <code>add_curried3</code>? It is a function that takes in a 149 - <code>u32</code> and returns a <code>u32</code>. So a 150 - <code>fn(u32) -&gt; u32</code> will do right? No, I’ll explain why in 151 - the next point, but for now, we will make use of the <code>Fn</code> 152 - trait, our return type is <code>impl Fn(u32) -&gt; u32</code>. This 153 - basically tells the compiler that we will be returning something 154 - function-like, a.k.a, behaves like a <code>Fn</code>. Cool!</p> 155 - <p>If you have been following along, you should be able to tell that the 156 - return type of <code>add_curried1</code> is:</p> 157 - <pre><code>impl Fn(u32) -&gt; (impl Fn(u32) -&gt; u32)</code></pre> 158 - <p>We can drop the parentheses because <code>-&gt;</code> is right 159 - associative:</p> 160 - <pre><code>impl Fn(u32) -&gt; impl Fn(u32) -&gt; u32 161 - </code></pre> 162 - <p><strong>Accessing environment</strong><br /> 163 - A function cannot access it’s environment. Our solution will not work. 164 - <code>add_curried3</code> attempts to access <code>x</code>, which is 165 - not allowed! A closure<a href="#fn1" class="footnote-ref" id="fnref1" 166 - role="doc-noteref"><sup>1</sup></a> however, can. If we are returning a 167 - closure, our return type must be <code>impl Fn</code>, and not 168 - <code>fn</code>. The difference between the <code>Fn</code> trait and 169 - function pointers is beyond the scope of this post.</p> 170 - <h3 id="refinement">Refinement</h3> 171 - <p>Armed with knowledge, we refine our expected output, this time, 172 - employing closures:</p> 173 - <div class="sourceCode" id="cb8"><pre 174 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> add(x<span class="op">:</span> <span class="dt">u32</span>) <span class="op">-&gt;</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> <span class="dt">u32</span> <span class="op">{</span></span> 175 - <span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="kw">move</span> <span class="op">|</span>y<span class="op">|</span> <span class="kw">move</span> <span class="op">|</span>z<span class="op">|</span> x <span class="op">+</span> y <span class="op">+</span> z<span class="op">;</span></span> 176 - <span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 177 - <p>Alas, that does not compile either! It errors out with the following 178 - message:</p> 179 - <pre><code>error[E0562]: `impl Trait` not allowed outside of function 180 - and inherent method return types 181 - --&gt; src/main.rs:17:37 182 - | 183 - | fn add(x: u32) -&gt; impl Fn(u32) -&gt; impl Fn(u32) -&gt; u32 184 - | ^^^^^^^^^^^^^^^^^^^ 185 - </code></pre> 186 - <p>You are allowed to return an <code>impl Fn</code> only inside a 187 - function. We are currently returning it from another return! Or at 188 - least, that was the most I could make out of the error message.</p> 189 - <p>We are going to have to cheat a bit to fix this issue; with type 190 - aliases and a convenient nightly feature <a href="#fn2" 191 - class="footnote-ref" id="fnref2" 192 - role="doc-noteref"><sup>2</sup></a>:</p> 193 - <div class="sourceCode" id="cb10"><pre 194 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="at">#![</span>feature<span class="at">(</span>type_alias_impl_trait<span class="at">)]</span> <span class="co">// allows us to use `impl Fn` in type aliases!</span></span> 195 - <span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a></span> 196 - <span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T0 <span class="op">=</span> <span class="dt">u32</span><span class="op">;</span> <span class="co">// the return value when zero args are to be applied</span></span> 197 - <span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T1 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> T0<span class="op">;</span> <span class="co">// the return value when one arg is to be applied</span></span> 198 - <span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T2 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> T1<span class="op">;</span> <span class="co">// the return value when two args are to be applied</span></span> 199 - <span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a></span> 200 - <span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> add(x<span class="op">:</span> <span class="dt">u32</span>) <span class="op">-&gt;</span> T2 <span class="op">{</span></span> 201 - <span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="kw">move</span> <span class="op">|</span>y<span class="op">|</span> <span class="kw">move</span> <span class="op">|</span>z<span class="op">|</span> x <span class="op">+</span> y <span class="op">+</span> z<span class="op">;</span></span> 202 - <span id="cb10-9"><a href="#cb10-9" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 203 - <p>Drop that into a cargo project, call <code>add(4)(5)(6)</code>, cross 204 - your fingers, and run <code>cargo +nightly run</code>. You should see a 205 - 15 unless you forgot to print it!</p> 206 - <h3 id="the-in-betweens">The In-Betweens</h3> 207 - <p>Let us write the magical bits that take us from function to curried 208 - function.</p> 209 - <p>Initialize your workspace with <code>cargo new --lib currying</code>. 210 - Proc-macro crates are libraries with exactly one export, the macro 211 - itself. Add a <code>tests</code> directory to your crate root. Your 212 - directory should look something like this:</p> 213 - <pre><code>. 214 - ├── Cargo.toml 215 - ├── src 216 - │   └── lib.rs 217 - └── tests 218 - └── smoke.rs</code></pre> 219 - <h4 id="dependencies">Dependencies</h4> 220 - <p>We will be using a total of 3 external crates:</p> 221 - <ul> 222 - <li><a 223 - href="https://docs.rs/proc-macro2/1.0.12/proc_macro2/">proc_macro2</a></li> 224 - <li><a href="https://docs.rs/syn/1.0.18/syn/index.html">syn</a></li> 225 - <li><a 226 - href="https://docs.rs/quote/1.0.4/quote/index.html">quote</a></li> 227 - </ul> 228 - <p>Here’s a sample <code>Cargo.toml</code>:</p> 229 - <pre><code># Cargo.toml 230 - 231 - [dependencies] 232 - proc-macro2 = &quot;1.0.9&quot; 233 - quote = &quot;1.0&quot; 234 - 235 - [dependencies.syn] 236 - version = &quot;1.0&quot; 237 - features = [&quot;full&quot;] 238 - 239 - [lib] 240 - proc-macro = true # this is important!</code></pre> 241 - <p>We will be using an external <code>proc-macro2</code> crate as well 242 - as an internal <code>proc-macro</code> crate. Not confusing at all!</p> 243 - <h4 id="the-attribute-macro">The attribute macro</h4> 244 - <p>Drop this into <code>src/lib.rs</code>, to get the ball rolling.</p> 245 - <div class="sourceCode" id="cb13"><pre 246 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="co">// src/lib.rs</span></span> 247 - <span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a></span> 248 - <span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">proc_macro::</span>TokenStream<span class="op">;</span> <span class="co">// 1</span></span> 249 - <span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">quote::</span>quote<span class="op">;</span></span> 250 - <span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">syn::</span><span class="op">{</span>parse_macro_input<span class="op">,</span> ItemFn<span class="op">};</span></span> 251 - <span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a></span> 252 - <span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a><span class="at">#[</span>proc_macro_attribute<span class="at">]</span> <span class="co">// 2</span></span> 253 - <span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a><span class="kw">pub</span> <span class="kw">fn</span> curry(_attr<span class="op">:</span> TokenStream<span class="op">,</span> item<span class="op">:</span> TokenStream) <span class="op">-&gt;</span> TokenStream <span class="op">{</span></span> 254 - <span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> parsed <span class="op">=</span> <span class="pp">parse_macro_input!</span>(item <span class="kw">as</span> ItemFn)<span class="op">;</span> <span class="co">// 3</span></span> 255 - <span id="cb13-10"><a href="#cb13-10" aria-hidden="true" tabindex="-1"></a> generate_curry(parsed)<span class="op">.</span>into() <span class="co">// 4</span></span> 256 - <span id="cb13-11"><a href="#cb13-11" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span> 257 - <span id="cb13-12"><a href="#cb13-12" aria-hidden="true" tabindex="-1"></a></span> 258 - <span id="cb13-13"><a href="#cb13-13" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> generate_curry(parsed<span class="op">:</span> ItemFn) <span class="op">-&gt;</span> <span class="pp">proc_macro2::</span>TokenStream <span class="op">{}</span></span></code></pre></div> 259 - <p><strong>1. Imports</strong></p> 260 - <p>A <code>Tokenstream</code> holds (hopefully valid) Rust code, this is 261 - the type of our input and output. Note that we are importing this type 262 - from <code>proc_macro</code> and not <code>proc_macro2</code>.</p> 263 - <p><code>quote!</code> from the <code>quote</code> crate is a macro that 264 - allows us to quickly produce <code>TokenStream</code>s. Much like the 265 - LISP <code>quote</code> procedure, you can use the <code>quote!</code> 266 - macro for symbolic transformations.</p> 267 - <p><code>ItemFn</code> from the <code>syn</code> crate holds the parsed 268 - <code>TokenStream</code> of a Rust function. 269 - <code>parse_macro_input!</code> is a helper macro provided by 270 - <code>syn</code>.</p> 271 - <p><strong>2. The lone export</strong></p> 272 - <p>Annotate the only <code>pub</code> of our crate with 273 - <code>#[proc_macro_attribute]</code>. This tells rustc that 274 - <code>curry</code> is a procedural macro, and allows us to use it as 275 - <code>#[crate_name::curry]</code> in other crates. Note the signature of 276 - the <code>curry</code> function. <code>_attr</code> is the 277 - <code>TokenStream</code> representing the attribute itself, 278 - <code>item</code> refers to the thing we slapped our macro into, in this 279 - case a function (like <code>add</code>). The return value is a modified 280 - <code>TokenStream</code>, this will contain our curried version of 281 - <code>add</code>.</p> 282 - <p><strong>3. The helper macro</strong></p> 283 - <p>A <code>TokenStream</code> is a little hard to work with, which is 284 - why we have the <code>syn</code> crate, which provides types to 285 - represent Rust tokens. An <code>RArrow</code> struct to represent the 286 - return arrow on a function and so on. One of those types is 287 - <code>ItemFn</code>, that represents an entire Rust function. The 288 - <code>parse_macro_input!</code> automatically puts the input to our 289 - macro into an <code>ItemFn</code>. What a gentleman!</p> 290 - <p><strong>4. Returning <code>TokenStream</code>s </strong></p> 291 - <p>We haven’t filled in <code>generate_curry</code> yet, but we can see 292 - that it returns a <code>proc_macro2::TokenStream</code> and not a 293 - <code>proc_macro::TokenStream</code>, so drop a <code>.into()</code> to 294 - convert it.</p> 295 - <p>Lets move on, and fill in <code>generate_curry</code>, I would 296 - suggest keeping the documentation for <a 297 - href="https://docs.rs/syn/1.0.19/syn/struct.ItemFn.html"><code>syn::ItemFn</code></a> 298 - and <a 299 - href="https://docs.rs/syn/1.0.19/syn/struct.Signature.html"><code>syn::Signature</code></a> 300 - open.</p> 301 - <div class="sourceCode" id="cb14"><pre 302 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="co">// src/lib.rs</span></span> 303 - <span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a></span> 304 - <span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> generate_curry(parsed<span class="op">:</span> ItemFn) <span class="op">-&gt;</span> <span class="pp">proc_macro2::</span>TokenStream <span class="op">{</span></span> 305 - <span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_body <span class="op">=</span> parsed<span class="op">.</span>block<span class="op">;</span> <span class="co">// function body</span></span> 306 - <span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> sig <span class="op">=</span> parsed<span class="op">.</span>sig<span class="op">;</span> <span class="co">// function signature</span></span> 307 - <span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> vis <span class="op">=</span> parsed<span class="op">.</span>vis<span class="op">;</span> <span class="co">// visibility, pub or not</span></span> 308 - <span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_name <span class="op">=</span> sig<span class="op">.</span>ident<span class="op">;</span> <span class="co">// function name/identifier</span></span> 309 - <span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_args <span class="op">=</span> sig<span class="op">.</span>inputs<span class="op">;</span> <span class="co">// comma separated args</span></span> 310 - <span id="cb14-9"><a href="#cb14-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_return_type <span class="op">=</span> sig<span class="op">.</span>output<span class="op">;</span> <span class="co">// return type</span></span> 311 - <span id="cb14-10"><a href="#cb14-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 312 - <p>We are simply extracting the bits of the function, we will be reusing 313 - the original function’s visibility and name. Take a look at what 314 - <code>syn::Signature</code> can tell us about a function:</p> 315 - <pre><code> .-- syn::Ident (ident) 316 - / 317 - fn add(x: u32, y: u32) -&gt; u32 318 - (fn_token) / ~~~~~~~,~~~~~~ ~~~~~~ 319 - syn::token::Fn --&#39; / \ (output) 320 - &#39; `- syn::ReturnType 321 - Punctuated&lt;FnArg, Comma&gt; (inputs)</code></pre> 322 - <p>Enough analysis, lets produce our first bit of Rust code.</p> 323 - <h4 id="function-body">Function Body</h4> 324 - <p>Recall that the body of a curried <code>add</code> should look like 325 - this:</p> 326 - <div class="sourceCode" id="cb16"><pre 327 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="cf">return</span> <span class="kw">move</span> <span class="op">|</span>y<span class="op">|</span> <span class="kw">move</span> <span class="op">|</span>z<span class="op">|</span> x <span class="op">+</span> y <span class="op">+</span> z<span class="op">;</span></span></code></pre></div> 328 - <p>And in general:</p> 329 - <div class="sourceCode" id="cb17"><pre 330 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="cf">return</span> <span class="kw">move</span> <span class="op">|</span>arg2<span class="op">|</span> <span class="kw">move</span> <span class="op">|</span>arg3<span class="op">|</span> <span class="op">...</span> <span class="op">|</span>argN<span class="op">|</span> <span class="op">&lt;</span>function body here<span class="op">&gt;</span></span></code></pre></div> 331 - <p>We already have the function’s body, provided by 332 - <code>fn_body</code>, in our <code>generate_curry</code> function. All 333 - that’s left to add is the <code>move |arg2| move |arg3| ...</code> 334 - stuff, for which we need to extract the argument identifiers (doc: <a 335 - href="https://docs.rs/syn/1.0.18/syn/punctuated/struct.Punctuated.html">Punctuated</a>, 336 - <a href="https://docs.rs/syn/1.0.18/syn/enum.FnArg.html">FnArg</a>, <a 337 - href="https://docs.rs/syn/1.0.18/syn/struct.PatType.html">PatType</a>):</p> 338 - <div class="sourceCode" id="cb18"><pre 339 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="co">// src/lib.rs</span></span> 340 - <span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">syn::punctuated::</span>Punctuated<span class="op">;</span></span> 341 - <span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">syn::</span><span class="op">{</span>parse_macro_input<span class="op">,</span> FnArg<span class="op">,</span> Pat<span class="op">,</span> ItemFn<span class="op">,</span> Block<span class="op">};</span></span> 342 - <span id="cb18-4"><a href="#cb18-4" aria-hidden="true" tabindex="-1"></a></span> 343 - <span id="cb18-5"><a href="#cb18-5" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> extract_arg_idents(fn_args<span class="op">:</span> Punctuated<span class="op">&lt;</span>FnArg<span class="op">,</span> <span class="pp">syn::token::</span>Comma<span class="op">&gt;</span>) <span class="op">-&gt;</span> <span class="dt">Vec</span><span class="op">&lt;</span><span class="dt">Box</span><span class="op">&lt;</span>Pat<span class="op">&gt;&gt;</span> <span class="op">{</span> </span> 344 - <span id="cb18-6"><a href="#cb18-6" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> fn_args<span class="op">.</span>into_iter()<span class="op">.</span>map(extract_arg_pat)<span class="op">.</span><span class="pp">collect::</span><span class="op">&lt;</span><span class="dt">Vec</span><span class="op">&lt;</span>_<span class="op">&gt;&gt;</span>()<span class="op">;</span></span> 345 - <span id="cb18-7"><a href="#cb18-7" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 346 - <p>Alright, so we are iterating over function args 347 - (<code>Punctuated</code> is a collection that you can iterate over) and 348 - mapping an <code>extract_arg_pat</code> to every item. What’s 349 - <code>extract_arg_pat</code>?</p> 350 - <div class="sourceCode" id="cb19"><pre 351 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="co">// src/lib.rs</span></span> 352 - <span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a></span> 353 - <span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> extract_arg_pat(a<span class="op">:</span> FnArg) <span class="op">-&gt;</span> <span class="dt">Box</span><span class="op">&lt;</span>Pat<span class="op">&gt;</span> <span class="op">{</span></span> 354 - <span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a> <span class="cf">match</span> a <span class="op">{</span></span> 355 - <span id="cb19-5"><a href="#cb19-5" aria-hidden="true" tabindex="-1"></a> <span class="pp">FnArg::</span>Typed(p) <span class="op">=&gt;</span> p<span class="op">.</span>pat<span class="op">,</span></span> 356 - <span id="cb19-6"><a href="#cb19-6" aria-hidden="true" tabindex="-1"></a> _ <span class="op">=&gt;</span> <span class="pp">panic!</span>(<span class="st">&quot;Not supported on types with `self`!&quot;</span>)<span class="op">,</span></span> 357 - <span id="cb19-7"><a href="#cb19-7" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 358 - <span id="cb19-8"><a href="#cb19-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 359 - <p><code>FnArg</code> is an enum type as you might have guessed. The 360 - <code>Typed</code> variant encompasses args that are written as 361 - <code>name: type</code> and the other variant, <code>Reciever</code> 362 - refers to <code>self</code> types. Ignore those for now, keep it 363 - simple.</p> 364 - <p>Every <code>FnArg::Typed</code> value contains a <code>pat</code>, 365 - which is in essence, the name of the argument. The type of the arg is 366 - accessible via <code>p.ty</code> (we will be using this later).</p> 367 - <p>With that done, we should be able to write the codegen for the 368 - function body:</p> 369 - <div class="sourceCode" id="cb20"><pre 370 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="co">// src/lib.rs</span></span> 371 - <span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a></span> 372 - <span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> generate_body(fn_args<span class="op">:</span> <span class="op">&amp;</span>[<span class="dt">Box</span><span class="op">&lt;</span>Pat<span class="op">&gt;</span>]<span class="op">,</span> body<span class="op">:</span> <span class="dt">Box</span><span class="op">&lt;</span>Block<span class="op">&gt;</span>) <span class="op">-&gt;</span> <span class="pp">proc_macro2::</span>TokenStream <span class="op">{</span></span> 373 - <span id="cb20-4"><a href="#cb20-4" aria-hidden="true" tabindex="-1"></a> <span class="pp">quote!</span> <span class="op">{</span></span> 374 - <span id="cb20-5"><a href="#cb20-5" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> #( <span class="kw">move</span> <span class="op">|</span>#fn_args<span class="op">|</span> )<span class="op">*</span> #body</span> 375 - <span id="cb20-6"><a href="#cb20-6" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 376 - <span id="cb20-7"><a href="#cb20-7" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 377 - <p>That is some scary looking syntax! Allow me to explain. The 378 - <code>quote!{ ... }</code> returns a 379 - <code>proc_macro2::TokenStream</code>, if we wrote 380 - <code>quote!{ let x = 1 + 2; }</code>, it wouldn’t create a new variable 381 - <code>x</code> with value 3, it would literally produce a stream of 382 - tokens with that expression.</p> 383 - <p>The <code>#</code> enables variable interpolation. <code>#body</code> 384 - will look for <code>body</code> in the current scope, take its value, 385 - and insert it in the returned <code>TokenStream</code>. Kinda like quasi 386 - quoting in LISPs, you have written one.</p> 387 - <p>What about <code>#( move |#fn_args| )*</code>? That is repetition. 388 - <code>quote</code> iterates through <code>fn_args</code>, and drops a 389 - <code>move</code> behind each one, it then places pipes 390 - (<code>|</code>), around it.</p> 391 - <p>Let us test our first bit of codegen! Modify 392 - <code>generate_curry</code> like so:</p> 393 - <div class="sourceCode" id="cb21"><pre 394 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true" tabindex="-1"></a><span class="co">// src/lib.rs</span></span> 395 - <span id="cb21-2"><a href="#cb21-2" aria-hidden="true" tabindex="-1"></a></span> 396 - <span id="cb21-3"><a href="#cb21-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">fn</span> generate_curry(parsed<span class="op">:</span> ItemFn) <span class="op">-&gt;</span> TokenStream <span class="op">{</span></span> 397 - <span id="cb21-4"><a href="#cb21-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_body <span class="op">=</span> parsed<span class="op">.</span>block<span class="op">;</span></span> 398 - <span id="cb21-5"><a href="#cb21-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> sig <span class="op">=</span> parsed<span class="op">.</span>sig<span class="op">;</span></span> 399 - <span id="cb21-6"><a href="#cb21-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> vis <span class="op">=</span> parsed<span class="op">.</span>vis<span class="op">;</span></span> 400 - <span id="cb21-7"><a href="#cb21-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_name <span class="op">=</span> sig<span class="op">.</span>ident<span class="op">;</span></span> 401 - <span id="cb21-8"><a href="#cb21-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_args <span class="op">=</span> sig<span class="op">.</span>inputs<span class="op">;</span></span> 402 - <span id="cb21-9"><a href="#cb21-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_return_type <span class="op">=</span> sig<span class="op">.</span>output<span class="op">;</span></span> 403 - <span id="cb21-10"><a href="#cb21-10" aria-hidden="true" tabindex="-1"></a></span> 404 - <span id="cb21-11"><a href="#cb21-11" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="kw">let</span> arg_idents <span class="op">=</span> extract_arg_idents(fn_args<span class="op">.</span>clone())<span class="op">;</span></span> 405 - <span id="cb21-12"><a href="#cb21-12" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="kw">let</span> first_ident <span class="op">=</span> <span class="op">&amp;</span>arg_idents<span class="op">.</span>first()<span class="op">.</span>unwrap()<span class="op">;</span></span> 406 - <span id="cb21-13"><a href="#cb21-13" aria-hidden="true" tabindex="-1"></a></span> 407 - <span id="cb21-14"><a href="#cb21-14" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="co">// remember, our curried body starts with the second argument!</span></span> 408 - <span id="cb21-15"><a href="#cb21-15" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="kw">let</span> curried_body <span class="op">=</span> generate_body(<span class="op">&amp;</span>arg_idents[<span class="dv">1</span><span class="op">..</span>]<span class="op">,</span> fn_body<span class="op">.</span>clone())<span class="op">;</span></span> 409 - <span id="cb21-16"><a href="#cb21-16" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> curried_body)<span class="op">;</span></span> 410 - <span id="cb21-17"><a href="#cb21-17" aria-hidden="true" tabindex="-1"></a></span> 411 - <span id="cb21-18"><a href="#cb21-18" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> <span class="pp">TokenStream::</span>new()<span class="op">;</span></span> 412 - <span id="cb21-19"><a href="#cb21-19" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span></code></pre></div> 413 - <p>Add a little test to <code>tests/</code>:</p> 414 - <div class="sourceCode" id="cb22"><pre 415 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true" tabindex="-1"></a><span class="co">// tests/smoke.rs</span></span> 416 - <span id="cb22-2"><a href="#cb22-2" aria-hidden="true" tabindex="-1"></a></span> 417 - <span id="cb22-3"><a href="#cb22-3" aria-hidden="true" tabindex="-1"></a><span class="at">#[</span><span class="pp">currying::</span>curry<span class="at">]</span></span> 418 - <span id="cb22-4"><a href="#cb22-4" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> add(x<span class="op">:</span> <span class="dt">u32</span><span class="op">,</span> y<span class="op">:</span> <span class="dt">u32</span><span class="op">,</span> z<span class="op">:</span> <span class="dt">u32</span>) <span class="op">-&gt;</span> <span class="dt">u32</span> <span class="op">{</span></span> 419 - <span id="cb22-5"><a href="#cb22-5" aria-hidden="true" tabindex="-1"></a> x <span class="op">+</span> y <span class="op">+</span> z</span> 420 - <span id="cb22-6"><a href="#cb22-6" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span> 421 - <span id="cb22-7"><a href="#cb22-7" aria-hidden="true" tabindex="-1"></a></span> 422 - <span id="cb22-8"><a href="#cb22-8" aria-hidden="true" tabindex="-1"></a><span class="at">#[</span>test<span class="at">]</span></span> 423 - <span id="cb22-9"><a href="#cb22-9" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> works() <span class="op">{</span></span> 424 - <span id="cb22-10"><a href="#cb22-10" aria-hidden="true" tabindex="-1"></a> <span class="pp">assert!</span>(<span class="cn">true</span>)<span class="op">;</span></span> 425 - <span id="cb22-11"><a href="#cb22-11" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 426 - <p>You should find something like this in the output of 427 - <code>cargo test</code>:</p> 428 - <pre><code>return move | y | move | z | { x + y + z }</code></pre> 429 - <p>Glorious <code>println!</code> debugging!</p> 430 - <h4 id="function-signature">Function signature</h4> 431 - <p>This section gets into the more complicated bits of the macro, 432 - generating type aliases and the function signature. By the end of this 433 - section, we should have a full working auto-currying macro!</p> 434 - <p>Recall what our generated type aliases should look like, for our 435 - <code>add</code> function:</p> 436 - <div class="sourceCode" id="cb24"><pre 437 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb24-1"><a href="#cb24-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T0 <span class="op">=</span> <span class="dt">u32</span><span class="op">;</span></span> 438 - <span id="cb24-2"><a href="#cb24-2" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T1 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> T0<span class="op">;</span></span> 439 - <span id="cb24-3"><a href="#cb24-3" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T2 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> T1<span class="op">;</span></span></code></pre></div> 440 - <p>In general:</p> 441 - <div class="sourceCode" id="cb25"><pre 442 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb25-1"><a href="#cb25-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T0 <span class="op">=</span> <span class="op">&lt;</span><span class="cf">return</span> <span class="kw">type</span>&gt;<span class="op">;</span></span> 443 - <span id="cb25-2"><a href="#cb25-2" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T1 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="op">&lt;</span><span class="kw">type</span> of arg N&gt;) -&gt; T0<span class="op">;</span></span> 444 - <span id="cb25-3"><a href="#cb25-3" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T2 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="op">&lt;</span><span class="kw">type</span> of arg N - 1&gt;) -&gt; T1<span class="op">;</span></span> 445 - <span id="cb25-4"><a href="#cb25-4" aria-hidden="true" tabindex="-1"></a><span class="op">.</span></span> 446 - <span id="cb25-5"><a href="#cb25-5" aria-hidden="true" tabindex="-1"></a><span class="op">.</span></span> 447 - <span id="cb25-6"><a href="#cb25-6" aria-hidden="true" tabindex="-1"></a><span class="op">.</span></span> 448 - <span id="cb25-7"><a href="#cb25-7" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T(N-1) <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="op">&lt;</span><span class="kw">type</span> of arg 2&gt;) -&gt; T(N-2)<span class="op">;</span></span></code></pre></div> 449 - <p>To codegen that, we need the types of:</p> 450 - <ul> 451 - <li>all our inputs (arguments)</li> 452 - <li>the output (the return type)</li> 453 - </ul> 454 - <p>To fetch the types of all our inputs, we can simply reuse the bits we 455 - wrote to fetch the names of all our inputs! (doc: <a 456 - href="https://docs.rs/syn/1.0.18/syn/enum.Type.html">Type</a>)</p> 457 - <div class="sourceCode" id="cb26"><pre 458 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb26-1"><a href="#cb26-1" aria-hidden="true" tabindex="-1"></a><span class="co">// src/lib.rs</span></span> 459 - <span id="cb26-2"><a href="#cb26-2" aria-hidden="true" tabindex="-1"></a></span> 460 - <span id="cb26-3"><a href="#cb26-3" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">syn::</span><span class="op">{</span>parse_macro_input<span class="op">,</span> Block<span class="op">,</span> FnArg<span class="op">,</span> ItemFn<span class="op">,</span> Pat<span class="op">,</span> ReturnType<span class="op">,</span> Type<span class="op">};</span></span> 461 - <span id="cb26-4"><a href="#cb26-4" aria-hidden="true" tabindex="-1"></a></span> 462 - <span id="cb26-5"><a href="#cb26-5" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> extract_type(a<span class="op">:</span> FnArg) <span class="op">-&gt;</span> <span class="dt">Box</span><span class="op">&lt;</span>Type<span class="op">&gt;</span> <span class="op">{</span></span> 463 - <span id="cb26-6"><a href="#cb26-6" aria-hidden="true" tabindex="-1"></a> <span class="cf">match</span> a <span class="op">{</span></span> 464 - <span id="cb26-7"><a href="#cb26-7" aria-hidden="true" tabindex="-1"></a> <span class="pp">FnArg::</span>Typed(p) <span class="op">=&gt;</span> p<span class="op">.</span>ty<span class="op">,</span> <span class="co">// notice `ty` instead of `pat`</span></span> 465 - <span id="cb26-8"><a href="#cb26-8" aria-hidden="true" tabindex="-1"></a> _ <span class="op">=&gt;</span> <span class="pp">panic!</span>(<span class="st">&quot;Not supported on types with `self`!&quot;</span>)<span class="op">,</span></span> 466 - <span id="cb26-9"><a href="#cb26-9" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 467 - <span id="cb26-10"><a href="#cb26-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span> 468 - <span id="cb26-11"><a href="#cb26-11" aria-hidden="true" tabindex="-1"></a></span> 469 - <span id="cb26-12"><a href="#cb26-12" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> extract_arg_types(fn_args<span class="op">:</span> Punctuated<span class="op">&lt;</span>FnArg<span class="op">,</span> <span class="pp">syn::token::</span>Comma<span class="op">&gt;</span>) <span class="op">-&gt;</span> <span class="dt">Vec</span><span class="op">&lt;</span><span class="dt">Box</span><span class="op">&lt;</span>Type<span class="op">&gt;&gt;</span> <span class="op">{</span></span> 470 - <span id="cb26-13"><a href="#cb26-13" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> fn_args<span class="op">.</span>into_iter()<span class="op">.</span>map(extract_type)<span class="op">.</span><span class="pp">collect::</span><span class="op">&lt;</span><span class="dt">Vec</span><span class="op">&lt;</span>_<span class="op">&gt;&gt;</span>()<span class="op">;</span></span> 471 - <span id="cb26-14"><a href="#cb26-14" aria-hidden="true" tabindex="-1"></a></span> 472 - <span id="cb26-15"><a href="#cb26-15" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 473 - <p>A good reader would have looked at the docs for output member of the 474 - <code>syn::Signature</code> struct. It has the type 475 - <code>syn::ReturnType</code>. So there is no extraction to do here 476 - right? There are actually a couple of things we have to ensure here:</p> 477 - <ol type="1"> 478 - <li><p>We need to ensure that the function returns! A function that does 479 - not return is pointless in this case, and I will tell you why, in the <a 480 - href="#notes">Notes</a> section.</p></li> 481 - <li><p>A <code>ReturnType</code> encloses the arrow of the return as 482 - well, we need to get rid of that. Recall:</p> 483 - <div class="sourceCode" id="cb27"><pre 484 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb27-1"><a href="#cb27-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T0 <span class="op">=</span> <span class="dt">u32</span></span> 485 - <span id="cb27-2"><a href="#cb27-2" aria-hidden="true" tabindex="-1"></a><span class="co">// and not</span></span> 486 - <span id="cb27-3"><a href="#cb27-3" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T0 <span class="op">=</span> <span class="op">-&gt;</span> <span class="dt">u32</span></span></code></pre></div></li> 487 - </ol> 488 - <p>Here is the snippet that handles extraction of the return type (doc: 489 - <a 490 - href="https://docs.rs/syn/1.0.19/syn/enum.ReturnType.html">syn::ReturnType</a>):</p> 491 - <div class="sourceCode" id="cb28"><pre 492 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb28-1"><a href="#cb28-1" aria-hidden="true" tabindex="-1"></a><span class="co">// src/lib.rs</span></span> 493 - <span id="cb28-2"><a href="#cb28-2" aria-hidden="true" tabindex="-1"></a></span> 494 - <span id="cb28-3"><a href="#cb28-3" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> extract_return_type(a<span class="op">:</span> ReturnType) <span class="op">-&gt;</span> <span class="dt">Box</span><span class="op">&lt;</span>Type<span class="op">&gt;</span> <span class="op">{</span></span> 495 - <span id="cb28-4"><a href="#cb28-4" aria-hidden="true" tabindex="-1"></a> <span class="cf">match</span> a <span class="op">{</span></span> 496 - <span id="cb28-5"><a href="#cb28-5" aria-hidden="true" tabindex="-1"></a> <span class="pp">ReturnType::</span>Type(_<span class="op">,</span> p) <span class="op">=&gt;</span> p<span class="op">,</span></span> 497 - <span id="cb28-6"><a href="#cb28-6" aria-hidden="true" tabindex="-1"></a> _ <span class="op">=&gt;</span> <span class="pp">panic!</span>(<span class="st">&quot;Not supported on functions without return types!&quot;</span>)<span class="op">,</span></span> 498 - <span id="cb28-7"><a href="#cb28-7" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 499 - <span id="cb28-8"><a href="#cb28-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 500 - <p>You might notice that we are making extensive use of the 501 - <code>panic!</code> macro. Well, that is because it is a good idea to 502 - quit on receiving an unsatisfactory <code>TokenStream</code>.</p> 503 - <p>With all our types ready, we can get on with generating type 504 - aliases:</p> 505 - <div class="sourceCode" id="cb29"><pre 506 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb29-1"><a href="#cb29-1" aria-hidden="true" tabindex="-1"></a><span class="co">// src/lib.rs</span></span> 507 - <span id="cb29-2"><a href="#cb29-2" aria-hidden="true" tabindex="-1"></a></span> 508 - <span id="cb29-3"><a href="#cb29-3" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">quote::</span><span class="op">{</span>quote<span class="op">,</span> format_ident<span class="op">};</span></span> 509 - <span id="cb29-4"><a href="#cb29-4" aria-hidden="true" tabindex="-1"></a></span> 510 - <span id="cb29-5"><a href="#cb29-5" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> generate_type_aliases(</span> 511 - <span id="cb29-6"><a href="#cb29-6" aria-hidden="true" tabindex="-1"></a> fn_arg_types<span class="op">:</span> <span class="op">&amp;</span>[<span class="dt">Box</span><span class="op">&lt;</span>Type<span class="op">&gt;</span>]<span class="op">,</span></span> 512 - <span id="cb29-7"><a href="#cb29-7" aria-hidden="true" tabindex="-1"></a> fn_return_type<span class="op">:</span> <span class="dt">Box</span><span class="op">&lt;</span>Type<span class="op">&gt;,</span></span> 513 - <span id="cb29-8"><a href="#cb29-8" aria-hidden="true" tabindex="-1"></a> fn_name<span class="op">:</span> <span class="op">&amp;</span><span class="pp">syn::</span>Ident<span class="op">,</span></span> 514 - <span id="cb29-9"><a href="#cb29-9" aria-hidden="true" tabindex="-1"></a>) <span class="op">-&gt;</span> <span class="dt">Vec</span><span class="op">&lt;</span><span class="pp">proc_macro2::</span>TokenStream<span class="op">&gt;</span> <span class="op">{</span> <span class="co">// 1</span></span> 515 - <span id="cb29-10"><a href="#cb29-10" aria-hidden="true" tabindex="-1"></a></span> 516 - <span id="cb29-11"><a href="#cb29-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> type_t0 <span class="op">=</span> <span class="pp">format_ident!</span>(<span class="st">&quot;_{}_T0&quot;</span><span class="op">,</span> fn_name)<span class="op">;</span> <span class="co">// 2</span></span> 517 - <span id="cb29-12"><a href="#cb29-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> <span class="kw">mut</span> type_aliases <span class="op">=</span> <span class="pp">vec!</span>[<span class="pp">quote!</span> <span class="op">{</span> <span class="kw">type</span> #type_t0 <span class="op">=</span> #fn_return_type <span class="op">}</span>]<span class="op">;</span></span> 518 - <span id="cb29-13"><a href="#cb29-13" aria-hidden="true" tabindex="-1"></a></span> 519 - <span id="cb29-14"><a href="#cb29-14" aria-hidden="true" tabindex="-1"></a> <span class="co">// 3</span></span> 520 - <span id="cb29-15"><a href="#cb29-15" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> (i<span class="op">,</span> t) <span class="kw">in</span> (<span class="dv">1</span><span class="op">..</span>)<span class="op">.</span>zip(fn_arg_types<span class="op">.</span>into_iter()<span class="op">.</span>rev()) <span class="op">{</span></span> 521 - <span id="cb29-16"><a href="#cb29-16" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> p <span class="op">=</span> <span class="pp">format_ident!</span>(<span class="st">&quot;_{}_{}&quot;</span><span class="op">,</span> fn_name<span class="op">,</span> <span class="pp">format!</span>(<span class="st">&quot;T{}&quot;</span><span class="op">,</span> i <span class="op">-</span> <span class="dv">1</span>))<span class="op">;</span></span> 522 - <span id="cb29-17"><a href="#cb29-17" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> n <span class="op">=</span> <span class="pp">format_ident!</span>(<span class="st">&quot;_{}_{}&quot;</span><span class="op">,</span> fn_name<span class="op">,</span> <span class="pp">format!</span>(<span class="st">&quot;T{}&quot;</span><span class="op">,</span> i))<span class="op">;</span></span> 523 - <span id="cb29-18"><a href="#cb29-18" aria-hidden="true" tabindex="-1"></a></span> 524 - <span id="cb29-19"><a href="#cb29-19" aria-hidden="true" tabindex="-1"></a> type_aliases<span class="op">.</span>push(<span class="pp">quote!</span> <span class="op">{</span></span> 525 - <span id="cb29-20"><a href="#cb29-20" aria-hidden="true" tabindex="-1"></a> <span class="kw">type</span> #n <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(#t) <span class="op">-&gt;</span> #p</span> 526 - <span id="cb29-21"><a href="#cb29-21" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span>)<span class="op">;</span></span> 527 - <span id="cb29-22"><a href="#cb29-22" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 528 - <span id="cb29-23"><a href="#cb29-23" aria-hidden="true" tabindex="-1"></a></span> 529 - <span id="cb29-24"><a href="#cb29-24" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> type_aliases<span class="op">;</span></span> 530 - <span id="cb29-25"><a href="#cb29-25" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 531 - <p><strong>1. The return value</strong><br /> 532 - We are returning a <code>Vec&lt;proc_macro2::TokenStream&gt;</code>, i. 533 - e., a list of <code>TokenStream</code>s, where each item is a type 534 - alias.</p> 535 - <p><strong>2. Format identifier?</strong><br /> 536 - I’ve got some explanation to do on this line. Clearly, we are trying to 537 - write the first type alias, and initialize our <code>TokenStream</code> 538 - vector with <code>T0</code>, because it is different from the 539 - others:</p> 540 - <div class="sourceCode" id="cb30"><pre 541 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb30-1"><a href="#cb30-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T0 <span class="op">=</span> something</span> 542 - <span id="cb30-2"><a href="#cb30-2" aria-hidden="true" tabindex="-1"></a><span class="co">// the others are of the form</span></span> 543 - <span id="cb30-3"><a href="#cb30-3" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> Tr <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(something) <span class="op">-&gt;</span> something</span></code></pre></div> 544 - <p><code>format_ident!</code> is similar to <code>format!</code>. 545 - Instead of returning a formatted string, it returns a 546 - <code>syn::Ident</code>. Therefore, <code>type_t0</code> is actually an 547 - identifier for, in the case of our <code>add</code> function, 548 - <code>_add_T0</code>. Why is this formatting important? Namespacing.</p> 549 - <p>Picture this, we have two functions, <code>add</code> and 550 - <code>subtract</code>, that we wish to curry with our macro:</p> 551 - <div class="sourceCode" id="cb31"><pre 552 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb31-1"><a href="#cb31-1" aria-hidden="true" tabindex="-1"></a><span class="at">#[</span>curry<span class="at">]</span></span> 553 - <span id="cb31-2"><a href="#cb31-2" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> add(<span class="op">...</span>) <span class="op">-&gt;</span> <span class="dt">u32</span> <span class="op">{</span> <span class="op">...</span> <span class="op">}</span></span> 554 - <span id="cb31-3"><a href="#cb31-3" aria-hidden="true" tabindex="-1"></a></span> 555 - <span id="cb31-4"><a href="#cb31-4" aria-hidden="true" tabindex="-1"></a><span class="at">#[</span>curry<span class="at">]</span></span> 556 - <span id="cb31-5"><a href="#cb31-5" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> sub(<span class="op">...</span>) <span class="op">-&gt;</span> <span class="dt">u32</span> <span class="op">{</span> <span class="op">...</span> <span class="op">}</span></span></code></pre></div> 557 - <p>Here is the same but with macros expanded:</p> 558 - <div class="sourceCode" id="cb32"><pre 559 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb32-1"><a href="#cb32-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T0 <span class="op">=</span> <span class="dt">u32</span><span class="op">;</span></span> 560 - <span id="cb32-2"><a href="#cb32-2" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T1 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> T0<span class="op">;</span></span> 561 - <span id="cb32-3"><a href="#cb32-3" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> add( <span class="op">...</span> ) <span class="op">-&gt;</span> T1 <span class="op">{</span> <span class="op">...</span> <span class="op">}</span></span> 562 - <span id="cb32-4"><a href="#cb32-4" aria-hidden="true" tabindex="-1"></a></span> 563 - <span id="cb32-5"><a href="#cb32-5" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T0 <span class="op">=</span> <span class="dt">u32</span><span class="op">;</span></span> 564 - <span id="cb32-6"><a href="#cb32-6" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> T1 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> T0<span class="op">;</span></span> 565 - <span id="cb32-7"><a href="#cb32-7" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> sub( <span class="op">...</span> ) <span class="op">-&gt;</span> T1 <span class="op">{</span> <span class="op">...</span> <span class="op">}</span></span></code></pre></div> 566 - <p>We end up with two definitions of <code>T0</code>! Now, if we do the 567 - little <code>format_ident!</code> dance we did up there:</p> 568 - <div class="sourceCode" id="cb33"><pre 569 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb33-1"><a href="#cb33-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> _add_T0 <span class="op">=</span> <span class="dt">u32</span><span class="op">;</span></span> 570 - <span id="cb33-2"><a href="#cb33-2" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> _add_T1 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> _add_T0<span class="op">;</span></span> 571 - <span id="cb33-3"><a href="#cb33-3" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> add( <span class="op">...</span> ) <span class="op">-&gt;</span> _add_T1 <span class="op">{</span> <span class="op">...</span> <span class="op">}</span></span> 572 - <span id="cb33-4"><a href="#cb33-4" aria-hidden="true" tabindex="-1"></a></span> 573 - <span id="cb33-5"><a href="#cb33-5" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> _sub_T0 <span class="op">=</span> <span class="dt">u32</span><span class="op">;</span></span> 574 - <span id="cb33-6"><a href="#cb33-6" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> _sub_T1 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> _sub_T0<span class="op">;</span></span> 575 - <span id="cb33-7"><a href="#cb33-7" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> sub( <span class="op">...</span> ) <span class="op">-&gt;</span> _sub_T1 <span class="op">{</span> <span class="op">...</span> <span class="op">}</span></span></code></pre></div> 576 - <p>Voilà! The type aliases don’t tread on each other. Remember to import 577 - <code>format_ident</code> from the <code>quote</code> crate.</p> 578 - <p><strong>3. The TokenStream Vector</strong></p> 579 - <p>We iterate over our types in reverse order (<code>T0</code> is the 580 - last return, <code>T1</code> is the second last, so on), assign a number 581 - to each iteration with <code>zip</code>, generate type names with 582 - <code>format_ident</code>, push a <code>TokenStream</code> with the help 583 - of <code>quote</code> and variable interpolation.</p> 584 - <p>If you are wondering why we used <code>(1..).zip()</code> instead of 585 - <code>.enumerate()</code>, it’s because we wanted to start counting from 586 - 1 instead of 0 (we are already done with <code>T0</code>!).</p> 587 - <h4 id="getting-it-together">Getting it together</h4> 588 - <p>I promised we’d have a fully working macro by the end of last 589 - section. I lied, we have to tie everything together in our 590 - <code>generate_curry</code> function:</p> 591 - <div class="sourceCode" id="cb34"><pre 592 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb34-1"><a href="#cb34-1" aria-hidden="true" tabindex="-1"></a><span class="co">// src/lib.rs</span></span> 593 - <span id="cb34-2"><a href="#cb34-2" aria-hidden="true" tabindex="-1"></a></span> 594 - <span id="cb34-3"><a href="#cb34-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">fn</span> generate_curry(parsed<span class="op">:</span> ItemFn) <span class="op">-&gt;</span> <span class="pp">proc_macro2::</span>TokenStream <span class="op">{</span></span> 595 - <span id="cb34-4"><a href="#cb34-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_body <span class="op">=</span> parsed<span class="op">.</span>block<span class="op">;</span></span> 596 - <span id="cb34-5"><a href="#cb34-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> sig <span class="op">=</span> parsed<span class="op">.</span>sig<span class="op">;</span></span> 597 - <span id="cb34-6"><a href="#cb34-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> vis <span class="op">=</span> parsed<span class="op">.</span>vis<span class="op">;</span></span> 598 - <span id="cb34-7"><a href="#cb34-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_name <span class="op">=</span> sig<span class="op">.</span>ident<span class="op">;</span></span> 599 - <span id="cb34-8"><a href="#cb34-8" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_args <span class="op">=</span> sig<span class="op">.</span>inputs<span class="op">;</span></span> 600 - <span id="cb34-9"><a href="#cb34-9" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> fn_return_type <span class="op">=</span> sig<span class="op">.</span>output<span class="op">;</span></span> 601 - <span id="cb34-10"><a href="#cb34-10" aria-hidden="true" tabindex="-1"></a></span> 602 - <span id="cb34-11"><a href="#cb34-11" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> arg_idents <span class="op">=</span> extract_arg_idents(fn_args<span class="op">.</span>clone())<span class="op">;</span></span> 603 - <span id="cb34-12"><a href="#cb34-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> first_ident <span class="op">=</span> <span class="op">&amp;</span>arg_idents<span class="op">.</span>first()<span class="op">.</span>unwrap()<span class="op">;</span></span> 604 - <span id="cb34-13"><a href="#cb34-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> curried_body <span class="op">=</span> generate_body(<span class="op">&amp;</span>arg_idents[<span class="dv">1</span><span class="op">..</span>]<span class="op">,</span> fn_body<span class="op">.</span>clone())<span class="op">;</span></span> 605 - <span id="cb34-14"><a href="#cb34-14" aria-hidden="true" tabindex="-1"></a></span> 606 - <span id="cb34-15"><a href="#cb34-15" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="kw">let</span> arg_types <span class="op">=</span> extract_arg_types(fn_args<span class="op">.</span>clone())<span class="op">;</span></span> 607 - <span id="cb34-16"><a href="#cb34-16" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="kw">let</span> first_type <span class="op">=</span> <span class="op">&amp;</span>arg_types<span class="op">.</span>first()<span class="op">.</span>unwrap()<span class="op">;</span></span> 608 - <span id="cb34-17"><a href="#cb34-17" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="kw">let</span> type_aliases <span class="op">=</span> generate_type_aliases(</span> 609 - <span id="cb34-18"><a href="#cb34-18" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="op">&amp;</span>arg_types[<span class="dv">1</span><span class="op">..</span>]<span class="op">,</span></span> 610 - <span id="cb34-19"><a href="#cb34-19" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> extract_return_type(fn_return_type)<span class="op">,</span></span> 611 - <span id="cb34-20"><a href="#cb34-20" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="op">&amp;</span>fn_name<span class="op">,</span></span> 612 - <span id="cb34-21"><a href="#cb34-21" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> )<span class="op">;</span></span> 613 - <span id="cb34-22"><a href="#cb34-22" aria-hidden="true" tabindex="-1"></a></span> 614 - <span id="cb34-23"><a href="#cb34-23" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="kw">let</span> return_type <span class="op">=</span> <span class="pp">format_ident!</span>(<span class="st">&quot;_{}_{}&quot;</span><span class="op">,</span> <span class="op">&amp;</span>fn_name<span class="op">,</span> <span class="pp">format!</span>(<span class="st">&quot;T{}&quot;</span><span class="op">,</span> type_aliases<span class="op">.</span>len() <span class="op">-</span> <span class="dv">1</span>))<span class="op">;</span></span> 615 - <span id="cb34-24"><a href="#cb34-24" aria-hidden="true" tabindex="-1"></a></span> 616 - <span id="cb34-25"><a href="#cb34-25" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="cf">return</span> <span class="pp">quote!</span> <span class="op">{</span></span> 617 - <span id="cb34-26"><a href="#cb34-26" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> #(#type_aliases)<span class="op">;*</span> <span class="op">;</span></span> 618 - <span id="cb34-27"><a href="#cb34-27" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> #vis <span class="kw">fn</span> #fn_name (#first_ident<span class="op">:</span> #first_type) <span class="op">-&gt;</span> #return_type <span class="op">{</span></span> 619 - <span id="cb34-28"><a href="#cb34-28" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> #curried_body <span class="op">;</span></span> 620 - <span id="cb34-29"><a href="#cb34-29" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="op">}</span></span> 621 - <span id="cb34-30"><a href="#cb34-30" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="op">};</span></span> 622 - <span id="cb34-31"><a href="#cb34-31" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span></code></pre></div> 623 - <p>Most of the additions are self explanatory, I’ll go through the 624 - return statement with you. We are returning a 625 - <code>quote!{ ... }</code>, so a <code>proc_macro2::TokenStream</code>. 626 - We are iterating through the <code>type_aliases</code> variable, which 627 - you might recall, is a <code>Vec&lt;TokenStream&gt;</code>. You might 628 - notice the sneaky semicolon before the <code>*</code>. This basically 629 - tells <code>quote</code>, to insert an item, then a semicolon, and then 630 - the next one, another semicolon, and so on. The semicolon is a 631 - separator. We need to manually insert another semicolon at the end of it 632 - all, <code>quote</code> doesn’t insert a separator at the end of the 633 - iteration.</p> 634 - <p>We retain the visibility and name of our original function. Our 635 - curried function takes as args, just the first argument of our original 636 - function. The return type of our curried function is actually, the last 637 - type alias we create. If you think back to our manually curried 638 - <code>add</code> function, we returned <code>T2</code>, which was in 639 - fact, the last type alias we created.</p> 640 - <p>I am sure, at this point, you are itching to test this out, but 641 - before that, let me introduce you to some good methods of debugging 642 - proc-macro code.</p> 643 - <h3 id="debugging-and-testing">Debugging and Testing</h3> 644 - <p>Install <code>cargo-expand</code> via:</p> 645 - <pre><code>cargo install cargo-expand</code></pre> 646 - <p><code>cargo-expand</code> is a neat little tool that expands your 647 - macro in places where it is used, and lets you view the generated code! 648 - For example:</p> 649 - <pre class="shell"><code># create a bin package hello 650 - $ cargo new hello 651 - 652 - # view the expansion of the println! macro 653 - $ cargo expand 654 - 655 - #![feature(prelude_import)] 656 - #[prelude_import] 657 - use std::prelude::v1::*; 658 - #[macro_use] 659 - extern crate std; 660 - fn main() { 661 - { 662 - ::std::io::_print(::core::fmt::Arguments::new_v1( 663 - &amp;[&quot;Hello, world!\n&quot;], 664 - &amp;match () { 665 - () =&gt; [], 666 - }, 667 - )); 668 - }; 669 - }</code></pre> 670 - <p>Writing proc-macros without <code>cargo-expand</code> is tantamount 671 - to driving a vehicle without rear view mirrors! Keep an eye on what is 672 - going on behind your back.</p> 673 - <p>Now, your macro won’t always compile, you might just recieve the bee 674 - movie script as an error. <code>cargo-expand</code> will not work in 675 - such cases. I would suggest printing out your variables to inspect them. 676 - <code>TokenStream</code> implements <code>Display</code> as well as 677 - <code>Debug</code>. We don’t always have to be respectable programmers. 678 - Just print it.</p> 679 - <p>Enough of that, lets get testing:</p> 680 - <div class="sourceCode" id="cb37"><pre 681 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb37-1"><a href="#cb37-1" aria-hidden="true" tabindex="-1"></a><span class="co">// tests/smoke.rs</span></span> 682 - <span id="cb37-2"><a href="#cb37-2" aria-hidden="true" tabindex="-1"></a></span> 683 - <span id="cb37-3"><a href="#cb37-3" aria-hidden="true" tabindex="-1"></a><span class="at">#![</span>feature<span class="at">(</span>type_alias_impl_trait<span class="at">)]</span></span> 684 - <span id="cb37-4"><a href="#cb37-4" aria-hidden="true" tabindex="-1"></a></span> 685 - <span id="cb37-5"><a href="#cb37-5" aria-hidden="true" tabindex="-1"></a><span class="at">#[</span><span class="pp">crate_name::</span>curry<span class="at">]</span></span> 686 - <span id="cb37-6"><a href="#cb37-6" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> add(x<span class="op">:</span> <span class="dt">u32</span><span class="op">,</span> y<span class="op">:</span> <span class="dt">u32</span><span class="op">,</span> z<span class="op">:</span> <span class="dt">u32</span>) <span class="op">-&gt;</span> <span class="dt">u32</span> <span class="op">{</span></span> 687 - <span id="cb37-7"><a href="#cb37-7" aria-hidden="true" tabindex="-1"></a> x <span class="op">+</span> y <span class="op">+</span> z</span> 688 - <span id="cb37-8"><a href="#cb37-8" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span> 689 - <span id="cb37-9"><a href="#cb37-9" aria-hidden="true" tabindex="-1"></a></span> 690 - <span id="cb37-10"><a href="#cb37-10" aria-hidden="true" tabindex="-1"></a><span class="at">#[</span>test<span class="at">]</span></span> 691 - <span id="cb37-11"><a href="#cb37-11" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> works() <span class="op">{</span></span> 692 - <span id="cb37-12"><a href="#cb37-12" aria-hidden="true" tabindex="-1"></a> <span class="pp">assert_eq!</span>(<span class="dv">15</span><span class="op">,</span> add(<span class="dv">4</span>)(<span class="dv">5</span>)(<span class="dv">6</span>))<span class="op">;</span></span> 693 - <span id="cb37-13"><a href="#cb37-13" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 694 - <p>Run <code>cargo +nightly test</code>. You should see a pleasing 695 - message:</p> 696 - <pre><code>running 1 test 697 - test tests::works ... ok</code></pre> 698 - <p>Take a look at the expansion for our curry macro, via 699 - <code>cargo +nightly expand --tests smoke</code>:</p> 700 - <div class="sourceCode" id="cb39"><pre 701 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb39-1"><a href="#cb39-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> _add_T0 <span class="op">=</span> <span class="dt">u32</span><span class="op">;</span></span> 702 - <span id="cb39-2"><a href="#cb39-2" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> _add_T1 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> _add_T0<span class="op">;</span></span> 703 - <span id="cb39-3"><a href="#cb39-3" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> _add_T2 <span class="op">=</span> <span class="kw">impl</span> <span class="bu">Fn</span>(<span class="dt">u32</span>) <span class="op">-&gt;</span> _add_T1<span class="op">;</span></span> 704 - <span id="cb39-4"><a href="#cb39-4" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> add(x<span class="op">:</span> <span class="dt">u32</span>) <span class="op">-&gt;</span> _add_T2 <span class="op">{</span></span> 705 - <span id="cb39-5"><a href="#cb39-5" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="kw">move</span> <span class="op">|</span>y<span class="op">|</span> <span class="op">{</span></span> 706 - <span id="cb39-6"><a href="#cb39-6" aria-hidden="true" tabindex="-1"></a> <span class="kw">move</span> <span class="op">|</span>z<span class="op">|</span> <span class="op">{</span></span> 707 - <span id="cb39-7"><a href="#cb39-7" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> x <span class="op">+</span> y <span class="op">+</span> z<span class="op">;</span></span> 708 - <span id="cb39-8"><a href="#cb39-8" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 709 - <span id="cb39-9"><a href="#cb39-9" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span>)<span class="op">;</span></span> 710 - <span id="cb39-10"><a href="#cb39-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span> 711 - <span id="cb39-11"><a href="#cb39-11" aria-hidden="true" tabindex="-1"></a></span> 712 - <span id="cb39-12"><a href="#cb39-12" aria-hidden="true" tabindex="-1"></a><span class="co">// a bunch of other stuff generated by #[test] and assert_eq!</span></span></code></pre></div> 713 - <p>A sight for sore eyes.</p> 714 - <p>Here is a more complex example that generates ten multiples of the 715 - first ten natural numbers:</p> 716 - <div class="sourceCode" id="cb40"><pre 717 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb40-1"><a href="#cb40-1" aria-hidden="true" tabindex="-1"></a><span class="at">#[</span>curry<span class="at">]</span></span> 718 - <span id="cb40-2"><a href="#cb40-2" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> product(x<span class="op">:</span> <span class="dt">u32</span><span class="op">,</span> y<span class="op">:</span> <span class="dt">u32</span>) <span class="op">-&gt;</span> <span class="dt">u32</span> <span class="op">{</span></span> 719 - <span id="cb40-3"><a href="#cb40-3" aria-hidden="true" tabindex="-1"></a> x <span class="op">*</span> y</span> 720 - <span id="cb40-4"><a href="#cb40-4" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span> 721 - <span id="cb40-5"><a href="#cb40-5" aria-hidden="true" tabindex="-1"></a></span> 722 - <span id="cb40-6"><a href="#cb40-6" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> multiples() <span class="op">-&gt;</span> <span class="dt">Vec</span><span class="op">&lt;</span><span class="dt">Vec</span><span class="op">&lt;</span><span class="dt">u32</span><span class="op">&gt;&gt;{</span></span> 723 - <span id="cb40-7"><a href="#cb40-7" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> v <span class="op">=</span> (<span class="dv">1</span><span class="op">..=</span><span class="dv">10</span>)<span class="op">.</span>map(product)<span class="op">;</span></span> 724 - <span id="cb40-8"><a href="#cb40-8" aria-hidden="true" tabindex="-1"></a> <span class="cf">return</span> (<span class="dv">1</span><span class="op">..=</span><span class="dv">10</span>)</span> 725 - <span id="cb40-9"><a href="#cb40-9" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>map(<span class="op">|</span>x<span class="op">|</span> v<span class="op">.</span>clone()<span class="op">.</span>map(<span class="op">|</span>f<span class="op">|</span> f(x))<span class="op">.</span>collect())</span> 726 - <span id="cb40-10"><a href="#cb40-10" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>collect()<span class="op">;</span></span> 727 - <span id="cb40-11"><a href="#cb40-11" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 728 - <h3 id="notes">Notes</h3> 729 - <p>I didn’t quite explain why we use <code>move |arg|</code> in our 730 - closure. This is because we want to take ownership of the variable 731 - supplied to us. Take a look at this example:</p> 732 - <div class="sourceCode" id="cb41"><pre 733 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb41-1"><a href="#cb41-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> v <span class="op">=</span> add(<span class="dv">5</span>)<span class="op">;</span></span> 734 - <span id="cb41-2"><a href="#cb41-2" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> g<span class="op">;</span></span> 735 - <span id="cb41-3"><a href="#cb41-3" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span> 736 - <span id="cb41-4"><a href="#cb41-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> x <span class="op">=</span> <span class="dv">5</span><span class="op">;</span></span> 737 - <span id="cb41-5"><a href="#cb41-5" aria-hidden="true" tabindex="-1"></a> g <span class="op">=</span> v(x)<span class="op">;</span></span> 738 - <span id="cb41-6"><a href="#cb41-6" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span> 739 - <span id="cb41-7"><a href="#cb41-7" aria-hidden="true" tabindex="-1"></a><span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> g(<span class="dv">2</span>))<span class="op">;</span></span></code></pre></div> 740 - <p>Variable <code>x</code> goes out of scope before <code>g</code> can 741 - return a concrete value. If we take ownership of <code>x</code> by 742 - <code>move</code>ing it into our closure, we can expect this to work 743 - reliably. In fact, rustc understands this, and forces you to use 744 - <code>move</code>.</p> 745 - <p>This usage of <code>move</code> is exactly why <strong>a curried 746 - function without a return is useless</strong>. Every variable we pass to 747 - our curried function gets moved into its local scope. Playing with these 748 - variables cannot cause a change outside this scope. Returning is our 749 - only method of interaction with anything beyond this function.</p> 750 - <h3 id="conclusion">Conclusion</h3> 751 - <p>Currying may not seem to be all that useful. Curried functions are 752 - unwieldy in Rust because the standard library is not built around 753 - currying. If you enjoy the possibilities posed by currying, consider 754 - taking a look at Haskell or Scheme.</p> 755 - <p>My original intention with <a href="https://peppe.rs">peppe.rs</a> 756 - was to post condensed articles, a micro blog, but this one turned out 757 - extra long.</p> 758 - <p>Perhaps I should call it a ‘macro’ blog :)</p> 759 - <section id="footnotes" class="footnotes footnotes-end-of-document" 760 - role="doc-endnotes"> 761 - <hr /> 762 - <ol> 763 - <li id="fn1"><p><a 764 - href="https://doc.rust-lang.org/book/ch13-01-closures.html">https://doc.rust-lang.org/book/ch13-01-closures.html</a><a 765 - href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li> 766 - <li id="fn2"><p><a href="https://caniuse.rs">caniuse.rs</a> contains an 767 - indexed list of features and their status.<a href="#fnref2" 768 - class="footnote-back" role="doc-backlink">↩︎</a></p></li> 769 - </ol> 770 - </section> 771 - 772 - </div> 773 - 774 - <div class="intro"> 775 - Hi. 776 - <div class="hot-links"> 777 - <a href="/index.xml" class="feed-button">Subscribe</a> 778 - </div> 779 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 780 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 781 - <p>Reach out at oppili@libera.chat.</p> 782 - </div> 783 - 784 - <a href="/" class="post-end-link">Home</a> 785 - <span>/</span> 786 - <a href="/posts" class="post-end-link">Posts</a> 787 - <span>/</span> 788 - <a class="post-end-link">Auto-currying Rust Functions</a> 789 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/auto-currying_rust_functions.md 790 - ">View Raw</a> 791 - </div> 792 - </div> 793 - </body> 794 - </html>
-110
docs/posts/bash_harder_with_vim/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Bash Harder With Vim"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/bash_harder_with_vim"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Bash Harder With Vim · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Bash Harder With Vim</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/bash_harder_with_vim.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 30/07 — 2019 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 24.38 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 1.6 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Bash Harder With Vim 43 - </h1> 44 - <div class="post-text"> 45 - <p>Bash is tricky, don’t let your editor get in your way. Here’s a 46 - couple of neat additions you could make to your <code>vimrc</code> for a 47 - better shell programming experience.</p> 48 - <h3 id="man-pages-inside-vim">Man pages inside vim</h3> 49 - <p>Source this script to get started:</p> 50 - <pre><code>runtime ftplugin/man.vim</code></pre> 51 - <p>Now, you can open manpages inside vim with <code>:Man</code>! It adds 52 - nicer syntax highlighting and the ability to jump around with 53 - <code>Ctrl-]</code> and <code>Ctrl-T</code>.</p> 54 - <p>By default, the manpage is opened in a horizontal split, I prefer 55 - using a new tab:</p> 56 - <pre><code>let g:ft_man_open_mode = &#39;tab&#39;</code></pre> 57 - <h3 id="scratchpad-to-test-your-commands">Scratchpad to test your 58 - commands</h3> 59 - <p>I often test my <code>sed</code> substitutions, here is a sample from 60 - the script used to generate this site:</p> 61 - <pre><code># a substitution to convert snake_case to Title Case With Spaces 62 - echo &quot;$1&quot; | sed -E -e &quot;s/\..+$//g&quot; -e &quot;s/_(.)/ \u\1/g&quot; -e &quot;s/^(.)/\u\1/g&quot;</code></pre> 63 - <p>Instead of dropping into a new shell, just test it out directly from 64 - vim!</p> 65 - <ul> 66 - <li>Yank the line into a register:</li> 67 - </ul> 68 - <pre><code>yy</code></pre> 69 - <ul> 70 - <li>Paste it into the command-line window:</li> 71 - </ul> 72 - <pre><code>q:p</code></pre> 73 - <ul> 74 - <li>Make edits as required:</li> 75 - </ul> 76 - <pre><code>syntax off # previously run commands 77 - edit index.html # in a buffer! 78 - w | so % 79 - !echo &quot;new_post.md&quot; | sed -E -e &quot;s/\..+$//g&quot; --snip-- 80 - ^--- note the use of &#39;!&#39;</code></pre> 81 - <ul> 82 - <li>Hit enter with the cursor on the line containing your command!</li> 83 - </ul> 84 - <pre><code>$ vim 85 - New Post # output 86 - Press ENTER or type command to continue</code></pre> 87 - 88 - </div> 89 - 90 - <div class="intro"> 91 - Hi. 92 - <div class="hot-links"> 93 - <a href="/index.xml" class="feed-button">Subscribe</a> 94 - </div> 95 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 96 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 97 - <p>Reach out at oppili@libera.chat.</p> 98 - </div> 99 - 100 - <a href="/" class="post-end-link">Home</a> 101 - <span>/</span> 102 - <a href="/posts" class="post-end-link">Posts</a> 103 - <span>/</span> 104 - <a class="post-end-link">Bash Harder With Vim</a> 105 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/bash_harder_with_vim.md 106 - ">View Raw</a> 107 - </div> 108 - </div> 109 - </body> 110 - </html>
-92
docs/posts/bye_bye_BDFs/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Bye Bye BDFs"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/bye_bye_BDFs"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Bye Bye BDFs · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Bye Bye BDFs</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/bye_bye_BDFs.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 07/08 — 2019 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 9.99 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 1.0 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Bye Bye BDFs 43 - </h1> 44 - <div class="post-text"> 45 - <p>Glyph Bitmap Distribution Format is no more, as the creators of <a 46 - href="https://pango.org">Pango</a>, one of the most widely used text 47 - rendering libraries, <a 48 - href="https://blogs.gnome.org/mclasen/2019/05/25/pango-future-directions/">announced</a> 49 - their plans for Pango 1.44.</p> 50 - <p>Until recently, Pango used FreeType to draw fonts. They will be 51 - moving over to <a href="https://harfbuzz.org">Harfbuzz</a>, an evolution 52 - of FreeType.</p> 53 - <p><em>Why?</em></p> 54 - <p>In short, FreeType was hard to work with. It required complex logic, 55 - and provided no advantage over Harfbuzz (other than being able to fetch 56 - opentype metrics with ease).</p> 57 - <p>Upgrading to Pango v1.44 will break your GTK applications (if you use 58 - a <code>bdf</code>/<code>pcf</code> bitmap font). Harfbuzz <em>does</em> 59 - support bitmap-only OpenType fonts, <code>otb</code>s. Convert your 60 - existing fonts over to <code>otb</code>s using <a 61 - href="https://fontforge.github.io">FontForge</a>. It is to be noted that 62 - applications such as <code>xterm</code> and <code>rxvt</code> use 63 - <code>xft</code> (X FreeType) to render fonts, and will remain 64 - unaffected by the update.</p> 65 - <p>Both <a 66 - href="https://github.com/nerdypepper/scientifica">scientifica</a> and <a 67 - href="https://github.com/nerdypepper/curie">curie</a> will soon ship 68 - with bitmap-only OpenType font formats.</p> 69 - 70 - </div> 71 - 72 - <div class="intro"> 73 - Hi. 74 - <div class="hot-links"> 75 - <a href="/index.xml" class="feed-button">Subscribe</a> 76 - </div> 77 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 78 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 79 - <p>Reach out at oppili@libera.chat.</p> 80 - </div> 81 - 82 - <a href="/" class="post-end-link">Home</a> 83 - <span>/</span> 84 - <a href="/posts" class="post-end-link">Posts</a> 85 - <span>/</span> 86 - <a class="post-end-link">Bye Bye BDFs</a> 87 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/bye_bye_BDFs.md 88 - ">View Raw</a> 89 - </div> 90 - </div> 91 - </body> 92 - </html>
-122
docs/posts/call_to_ARMs/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Call To ARMs"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/call_to_ARMs"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Call To ARMs · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Call To ARMs</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/call_to_ARMs.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 07/02 — 2020 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 33.57 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 2.3 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Call To ARMs 43 - </h1> 44 - <div class="post-text"> 45 - <p>My 4th semester involves ARM programming. And proprietary tooling 46 - (Keil C). But we don’t do that here.</p> 47 - <h3 id="building">Building</h3> 48 - <p>Assembling and linking ARM binaries on non-ARM architecture devices 49 - is fairly trivial. I went along with the GNU cross bare metal toolchain 50 - binutils, which provides <code>arm-as</code> and <code>arm-ld</code> 51 - (among a bunch of other utils that I don’t care about for now).</p> 52 - <p>Assemble <code>.s</code> files with:</p> 53 - <pre class="shell"><code>arm-none-eabi-as main.s -g -march=armv8.1-a -o main.out</code></pre> 54 - <p>The <code>-g</code> flag generates extra debugging information that 55 - <code>gdb</code> picks up. The <code>-march</code> option establishes 56 - target architecture.</p> 57 - <p>Link <code>.o</code> files with:</p> 58 - <pre class="shell"><code>arm-none-eabi-ld main.out -o main</code></pre> 59 - <h3 id="running-and-debugging">Running (and Debugging)</h3> 60 - <p>Things get interesting here. <code>gdb</code> on your x86 machine 61 - cannot read nor execute binaries compiled for ARM. So, we simulate an 62 - ARM processor using <code>qemu</code>. Now qemu allows you to run 63 - <code>gdbserver</code> on startup. Connecting our local <code>gdb</code> 64 - instance to <code>gdbserver</code> gives us a view into the program’s 65 - execution. Easy!</p> 66 - <p>Run <code>qemu</code>, with <code>gdbserver</code> on port 67 - <code>1234</code>, with our ARM binary, <code>main</code>:</p> 68 - <pre class="shell"><code>qemu-arm -singlestep -g 1234 main</code></pre> 69 - <p>Start up <code>gdb</code> on your machine, and connect to 70 - <code>qemu</code>’s <code>gdbserver</code>:</p> 71 - <pre><code>(gdb) set architecture armv8-a 72 - (gdb) target remote localhost:1234 73 - (gdb) file main 74 - Reading symbols from main... # yay!</code></pre> 75 - <h3 id="gdb-enhanced">GDB Enhanced</h3> 76 - <p><code>gdb</code> is cool, but it’s not nearly as comfortable as well 77 - fleshed out emulators/IDEs like Keil. Watching registers, CPSR and 78 - memory chunks update <em>is</em> pretty fun.</p> 79 - <p>I came across <code>gdb</code>’s TUI mode (hit <code>C-x C-a</code> 80 - or type <code>tui enable</code> at the prompt). TUI mode is a godsend. 81 - It highlights the current line of execution, shows you disassembly 82 - outputs, updated registers, active breakpoints and more.</p> 83 - <p><em>But</em>, it is an absolute eyesore.</p> 84 - <p>Say hello to <a href="https://github.com/hugsy/gef">GEF</a>! “GDB 85 - Enhanced Features” teaches our old dog some cool new tricks. Here are 86 - some additions that made my ARM debugging experience loads better:</p> 87 - <ul> 88 - <li>Memory watches</li> 89 - <li>Register watches, with up to 7 levels of deref (overkill, I 90 - agree)</li> 91 - <li>Stack tracing</li> 92 - </ul> 93 - <p>And it’s pretty! See for yourself:</p> 94 - <p><a href="https://u.peppe.rs/wq.png"><img 95 - src="https://u.peppe.rs/wq.png" /></a></p> 96 - <h3 id="editing">Editing</h3> 97 - <p>Vim, with <code>syntax off</code> because it dosen’t handle GNU ARM 98 - syntax too well.</p> 99 - 100 - </div> 101 - 102 - <div class="intro"> 103 - Hi. 104 - <div class="hot-links"> 105 - <a href="/index.xml" class="feed-button">Subscribe</a> 106 - </div> 107 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 108 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 109 - <p>Reach out at oppili@libera.chat.</p> 110 - </div> 111 - 112 - <a href="/" class="post-end-link">Home</a> 113 - <span>/</span> 114 - <a href="/posts" class="post-end-link">Posts</a> 115 - <span>/</span> 116 - <a class="post-end-link">Call To ARMs</a> 117 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/call_to_ARMs.md 118 - ">View Raw</a> 119 - </div> 120 - </div> 121 - </body> 122 - </html>
-94
docs/posts/color_conundrum/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Color Conundrum"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/color_conundrum"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Color Conundrum · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Color Conundrum</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/color_conundrum.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 30/12 — 2019 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 14.39 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 1.4 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Color Conundrum 43 - </h1> 44 - <div class="post-text"> 45 - <p>This piece aims to highlight (pun intended) some of the reasons 46 - behind my <a href="https://u.peppe.rs/bF.png">color free</a> editor 47 - setup.</p> 48 - <p>Imagine highlighting an entire book because <em>all</em> of it is 49 - important. That is exactly what (most) syntax highlighting does. It is 50 - difficult for the human eye to filter out noise in rainbow barf. Use 51 - color to draw attention, not diverge it.</p> 52 - <p>At the same time, a book devoid of color is <em>boring!</em> What is 53 - the takeaway from this 10 line paragraph? What are the technical terms 54 - used?</p> 55 - <p>Prose and code are certainly different, but the fickle minded human 56 - eye is the same. The eye constantly looks for a frame of reference, a 57 - focal point. It grows tired when it can’t find one.</p> 58 - <p>The following comparison does a better job of explaining (none, ample 59 - and over-the-top highlighting, from left to right):</p> 60 - <p><a href="https://u.peppe.rs/lt.png"><img 61 - src="https://u.peppe.rs/lt.png" /></a></p> 62 - <p>Without highlighting (far left), it is hard to differentiate between 63 - comments and code! The florid color scheme (far right) is no good 64 - either, it contains too many attention grabbers. The center sample is a 65 - healthy balance of both. Function calls and constants stand out, and 66 - repetitive keywords and other noise (<code>let</code>, <code>as</code>) 67 - are mildly dimmed out. Comments and non-code text (sign column, status 68 - text) are dimmed further.</p> 69 - <p>I’ll stop myself before I rant about color contrast and 70 - combinations.</p> 71 - 72 - </div> 73 - 74 - <div class="intro"> 75 - Hi. 76 - <div class="hot-links"> 77 - <a href="/index.xml" class="feed-button">Subscribe</a> 78 - </div> 79 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 80 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 81 - <p>Reach out at oppili@libera.chat.</p> 82 - </div> 83 - 84 - <a href="/" class="post-end-link">Home</a> 85 - <span>/</span> 86 - <a href="/posts" class="post-end-link">Posts</a> 87 - <span>/</span> 88 - <a class="post-end-link">Color Conundrum</a> 89 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/color_conundrum.md 90 - ">View Raw</a> 91 - </div> 92 - </div> 93 - </body> 94 - </html>
-360
docs/posts/configuring_jujutsu/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Configuring Jujutsu"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/configuring_jujutsu"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Configuring Jujutsu · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Configuring Jujutsu</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/configuring_jujutsu.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 24/05 — 2025 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 145.85 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 9.5 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Configuring Jujutsu 43 - </h1> 44 - <div class="post-text"> 45 - <p>There are a lot of reasons to use <a 46 - href="https://github.com/jj-vcs/jj">jujutsu</a>, but this post is not 47 - about that. I have this terrible habit of turning every knob on a tool 48 - before I even fully absorb how it works; and boy does <code>jj</code> 49 - have a lot of knobs.</p> 50 - <h3 id="templates">Templates</h3> 51 - <p><code>jj</code> let you tweak nearly every single character of its 52 - output. In fact, <code>jj</code> includes a full-blown templating 53 - language to do so. Lets start from the basics:</p> 54 - <p>Format all timestamps in relative fashion, like “5 hours ago”:</p> 55 - <div class="sourceCode" id="cb1"><pre 56 - class="sourceCode toml"><code class="sourceCode toml"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[template-aliases]</span></span> 57 - <span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="dt">&quot;format_timestamp(timestamp)&quot;</span> <span class="op">=</span> <span class="st">&quot;timestamp.ago()&quot;</span><span class="er">;</span></span></code></pre></div> 58 - <p>The default nodes in the log graph are a bit large for my taste, we 59 - can modify the <code>log_node</code> template to fix that:</p> 60 - <div class="sourceCode" id="cb2"><pre 61 - class="sourceCode toml"><code class="sourceCode toml"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[templates]</span></span> 62 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="dt">log_node</span> <span class="op">=</span> <span class="st">&#39;&#39;&#39;</span></span> 63 - <span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="vs"> label(&quot;node&quot;,</span></span> 64 - <span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="vs"> coalesce(</span></span> 65 - <span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="vs"> if(!self, label(&quot;elided&quot;, &quot;~&quot;)),</span></span> 66 - <span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="vs"> if(current_working_copy, label(&quot;working_copy&quot;, &quot;@&quot;)),</span></span> 67 - <span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="vs"> if(conflict, label(&quot;conflict&quot;, &quot;×&quot;)),</span></span> 68 - <span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="vs"> if(immutable, label(&quot;immutable&quot;, &quot;*&quot;)),</span></span> 69 - <span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="vs"> label(&quot;normal&quot;, &quot;·&quot;)</span></span> 70 - <span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="vs"> )</span></span> 71 - <span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a><span class="vs"> )</span></span> 72 - <span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a><span class="st">&#39;&#39;&#39;</span></span></code></pre></div> 73 - <p>This uses smaller iconography in <code>jj log</code> (these icons are 74 - also more widely available in fonts, in my experience):</p> 75 - <pre><code>@ wuuownsw me@oppi.li 21 minutes ago 30d1bd12 76 - │ (no description set) 77 - · plmznxvy me@oppi.li 5 hours ago push-plmznxvyqrqw git_head() 051c142e 78 - │ appview: pulls: bump sourceRev for stacks without causing resubmits 79 - │ * towvwqxk x@icyphox.sh 4 hours ago master a588f625 80 - │ │ appview: pages/markup: don&#39;t double camo in post process 81 - │ * ryzqnyvs me@oppi.li 4 hours ago ea4b520a 82 - │ │ appview: fix stack merging 83 - │ * kqvutzxr me@oppi.li 4 hours ago ff73ca23 84 - ├─╯ appview: rework RepoLanguages 85 - * vwpqwmms jeynesbrook@gmail.com 8 hours ago d759587b 86 - │ appview: repo: inject language percentage into repo index 87 - ~</code></pre> 88 - <p>Much nicer! And speaking of <code>log</code>, I find it hard to parse 89 - the output when every change occupies two lines instead of just one 90 - line, we can remedy that quickly; in fact, <code>jj</code> has a builtin 91 - template for single-line log outputs:</p> 92 - <pre><code>λ jj log -T builtin_log_oneline 93 - @ wuuownsw me@oppi.li 22 minutes ago 30d1bd12 (no description set) 94 - · plmznxvy me@oppi.li 5 hours ago push-plmznxvyqrqw git_head() 051c142e appview: pulls: bump sourceRev for stacks without causing resubmits 95 - │ * towvwqxk x@icyphox.sh 4 hours ago master a588f625 appview: pages/markup: don&#39;t double camo in post process 96 - │ * ryzqnyvs me@oppi.li 4 hours ago ea4b520a appview: fix stack merging 97 - │ * kqvutzxr me@oppi.li 4 hours ago ff73ca23 appview: rework RepoLanguages 98 - ├─╯ 99 - * vwpqwmms jeynesbrook 8 hours ago d759587b appview: repo: inject language percentage into repo index 100 - 101 - ~</code></pre> 102 - <p>Bit too long! I personally do not always need author and time 103 - information when quickly scrolling through the log, so I created my own 104 - template based on <code>builtin_log_oneline</code>:</p> 105 - <div class="sourceCode" id="cb5"><pre 106 - class="sourceCode toml"><code class="sourceCode toml"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[templates]</span></span> 107 - <span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="dt">log</span> <span class="op">=</span> <span class="st">&#39;&#39;&#39;</span></span> 108 - <span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="vs"> if(root,</span></span> 109 - <span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="vs"> format_root_commit(self),</span></span> 110 - <span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a><span class="vs"> label(if(current_working_copy, &quot;working_copy&quot;),</span></span> 111 - <span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a><span class="vs"> concat(</span></span> 112 - <span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="vs"> separate(&quot; &quot;,</span></span> 113 - <span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="vs"> format_short_change_id_with_hidden_and_divergent_info(self),</span></span> 114 - <span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="vs"> if(empty, label(&quot;empty&quot;, &quot;(empty)&quot;)),</span></span> 115 - <span id="cb5-10"><a href="#cb5-10" aria-hidden="true" tabindex="-1"></a><span class="vs"> if(description,</span></span> 116 - <span id="cb5-11"><a href="#cb5-11" aria-hidden="true" tabindex="-1"></a><span class="vs"> description.first_line(),</span></span> 117 - <span id="cb5-12"><a href="#cb5-12" aria-hidden="true" tabindex="-1"></a><span class="vs"> label(if(empty, &quot;empty&quot;), description_placeholder),</span></span> 118 - <span id="cb5-13"><a href="#cb5-13" aria-hidden="true" tabindex="-1"></a><span class="vs"> ),</span></span> 119 - <span id="cb5-14"><a href="#cb5-14" aria-hidden="true" tabindex="-1"></a><span class="vs"> bookmarks,</span></span> 120 - <span id="cb5-15"><a href="#cb5-15" aria-hidden="true" tabindex="-1"></a><span class="vs"> tags,</span></span> 121 - <span id="cb5-16"><a href="#cb5-16" aria-hidden="true" tabindex="-1"></a><span class="vs"> working_copies,</span></span> 122 - <span id="cb5-17"><a href="#cb5-17" aria-hidden="true" tabindex="-1"></a><span class="vs"> if(git_head, label(&quot;git_head&quot;, &quot;HEAD&quot;)),</span></span> 123 - <span id="cb5-18"><a href="#cb5-18" aria-hidden="true" tabindex="-1"></a><span class="vs"> if(conflict, label(&quot;conflict&quot;, &quot;conflict&quot;)),</span></span> 124 - <span id="cb5-19"><a href="#cb5-19" aria-hidden="true" tabindex="-1"></a><span class="vs"> if(config(&quot;ui.show-cryptographic-signatures&quot;).as_boolean(),</span></span> 125 - <span id="cb5-20"><a href="#cb5-20" aria-hidden="true" tabindex="-1"></a><span class="vs"> format_short_cryptographic_signature(signature)),</span></span> 126 - <span id="cb5-21"><a href="#cb5-21" aria-hidden="true" tabindex="-1"></a><span class="vs"> ) ++ &quot;\n&quot;,</span></span> 127 - <span id="cb5-22"><a href="#cb5-22" aria-hidden="true" tabindex="-1"></a><span class="vs"> ),</span></span> 128 - <span id="cb5-23"><a href="#cb5-23" aria-hidden="true" tabindex="-1"></a><span class="vs"> )</span></span> 129 - <span id="cb5-24"><a href="#cb5-24" aria-hidden="true" tabindex="-1"></a><span class="vs"> )</span></span> 130 - <span id="cb5-25"><a href="#cb5-25" aria-hidden="true" tabindex="-1"></a><span class="st">&#39;&#39;&#39;</span></span></code></pre></div> 131 - <p>Which produces:</p> 132 - <pre><code>@ wuuownsw (no description set) 133 - · plmznxvy appview: pulls: bump sourceRev for stacks without causing resubmits push-plmznxvyqrqw HEAD 134 - │ * towvwqxk appview: pages/markup: don&#39;t double camo in post process master 135 - │ * ryzqnyvs appview: fix stack merging 136 - │ * kqvutzxr appview: rework RepoLanguages 137 - ├─╯ 138 - * vwpqwmms appview: repo: inject language percentage into repo index 139 - 140 - ~</code></pre> 141 - <p>Sweet! To get a more detailed log, you can always use a different 142 - template for the output: 143 - <code>jj log -T builtin_log_detailed</code>.</p> 144 - <p>With git, I set <code>commit.verbose</code> to true, this lets me 145 - view the diff when composing a commit messaege in my 146 - <code>$EDITOR</code>:</p> 147 - <div class="sourceCode" id="cb7"><pre 148 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> git commit</span> 149 - <span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="co"># no message passed, opens $EDITOR to compose message</span></span> 150 - <span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a></span> 151 - <span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="co"># -- inside vim --</span></span> 152 - <span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a></span> 153 - <span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a><span class="co"># Please enter the commit message for your changes. Lines starting</span></span> 154 - <span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a><span class="co"># with &#39;#&#39; will be ignored, and an empty message aborts the commit.</span></span> 155 - <span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a><span class="co">#</span></span> 156 - <span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a><span class="co"># On branch trunk</span></span> 157 - <span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a><span class="co"># Changes to be committed:</span></span> 158 - <span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a><span class="co"># new file: scripts/handle.js</span></span> 159 - <span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a><span class="co">#</span></span> 160 - <span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a><span class="co"># ------------------------ &gt;8 ------------------------</span></span> 161 - <span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a><span class="co"># Do not modify or remove the line above.</span></span> 162 - <span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a><span class="co"># Everything below it will be ignored.</span></span> 163 - <span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a><span class="fu">diff</span> <span class="at">--git</span> a/scripts/handle.js b/scripts/handle.js</span> 164 - <span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a><span class="ex">new</span> file mode 100644</span> 165 - <span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a><span class="ex">index</span> 0000000..d8a07f3</span> 166 - <span id="cb7-19"><a href="#cb7-19" aria-hidden="true" tabindex="-1"></a><span class="ex">---</span> /dev/null</span> 167 - <span id="cb7-20"><a href="#cb7-20" aria-hidden="true" tabindex="-1"></a><span class="ex">+++</span> b/scripts/handle.js</span> 168 - <span id="cb7-21"><a href="#cb7-21" aria-hidden="true" tabindex="-1"></a><span class="ex">@@</span> <span class="at">-0,0</span> +1,104 @@</span> 169 - <span id="cb7-22"><a href="#cb7-22" aria-hidden="true" tabindex="-1"></a><span class="ex">+//</span> Run using node handle.js</span> 170 - <span id="cb7-23"><a href="#cb7-23" aria-hidden="true" tabindex="-1"></a><span class="ex">+//</span> Install axios using npm install axios</span> 171 - <span id="cb7-24"><a href="#cb7-24" aria-hidden="true" tabindex="-1"></a><span class="ex">+//</span> nodejs v18+</span> 172 - <span id="cb7-25"><a href="#cb7-25" aria-hidden="true" tabindex="-1"></a><span class="ex">+const</span> axios = require<span class="er">(</span><span class="st">&quot;axios&quot;</span><span class="kw">);</span></span> 173 - <span id="cb7-26"><a href="#cb7-26" aria-hidden="true" tabindex="-1"></a><span class="bu">.</span></span> 174 - <span id="cb7-27"><a href="#cb7-27" aria-hidden="true" tabindex="-1"></a><span class="bu">.</span></span> 175 - <span id="cb7-28"><a href="#cb7-28" aria-hidden="true" tabindex="-1"></a><span class="bu">.</span></span></code></pre></div> 176 - <p>The equivalent in <code>jj</code> requires modifying the 177 - <code>draft_commit_description</code> template:</p> 178 - <div class="sourceCode" id="cb8"><pre 179 - class="sourceCode toml"><code class="sourceCode toml"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[templates]</span></span> 180 - <span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="dt">draft_commit_description</span> <span class="op">=</span><span class="st">&#39;&#39;&#39;</span></span> 181 - <span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="vs"> concat(</span></span> 182 - <span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="vs"> coalesce(description, default_commit_description, &quot;\n&quot;),</span></span> 183 - <span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="vs"> surround(</span></span> 184 - <span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a><span class="vs"> &quot;\nJJ: This commit contains the following changes:\n&quot;, &quot;&quot;,</span></span> 185 - <span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a><span class="vs"> indent(&quot;JJ: &quot;, diff.stat(72)),</span></span> 186 - <span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a><span class="vs"> ),</span></span> 187 - <span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a><span class="vs"> &quot;\nJJ: ignore-rest\n&quot;,</span></span> 188 - <span id="cb8-10"><a href="#cb8-10" aria-hidden="true" tabindex="-1"></a><span class="vs"> diff.git(),</span></span> 189 - <span id="cb8-11"><a href="#cb8-11" aria-hidden="true" tabindex="-1"></a><span class="vs"> )</span></span> 190 - <span id="cb8-12"><a href="#cb8-12" aria-hidden="true" tabindex="-1"></a><span class="st">&#39;&#39;&#39;</span></span></code></pre></div> 191 - <h3 id="revsets">Revsets</h3> 192 - <p>As a not-so-power-user, I presently only use revsets to improve my 193 - <code>jj log</code> experience. In the context of <code>jj log</code>, 194 - giving it a revset means “here is a bag of revisions, graph them for me 195 - neatly”.</p> 196 - <p>I find myself using the “rebase” flow quite often:</p> 197 - <ul> 198 - <li>I hack on a stack of changes</li> 199 - <li><code>trunk</code> is updated by my fellow collaborators</li> 200 - <li>I rebase my stack using 201 - <code>jj rebase -s &lt;mine&gt; -d &lt;trunk&gt;</code></li> 202 - </ul> 203 - <p>And I scrub through the log output to roughly figure out how much 204 - work it would be to rebase, what I need for this is:</p> 205 - <ul> 206 - <li>the changes added to <code>trunk</code> since I last diverged from 207 - it</li> 208 - <li>the changes add to my stack since I last diverged from 209 - <code>trunk</code></li> 210 - </ul> 211 - <pre><code> X--Y--Z my stack 212 - / 213 - F--A--B--C--D trunk</code></pre> 214 - <p>If you want <code>jj log</code> to print this (and by “this”, I mean, 215 - the graph above, exactly as presented), you have to supply it a 216 - <code>revset</code> argument that grabs X, Y, Z, A, B, C, D and F. Some 217 - examples of revsets are:</p> 218 - <pre><code>@ # the rev marking the working copy 219 - x # the rev identified by shorthand `x` 220 - x | y # the set of two revs, [x, y] 221 - @ | trunk() # the set of two revs, [@, trunk()] 222 - .. # everything in this repository 223 - all() # also everything in this repository 224 - fork_point(@ | trunk()) # the rev where @ and trunk() forked off</code></pre> 225 - <p>And the revset that captures “ahead-behind” style output is:</p> 226 - <pre><code>trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)</code></pre> 227 - <p>Which includes:</p> 228 - <ul> 229 - <li><code>trunk()..@</code>: the new changes that my collaborators have 230 - added</li> 231 - <li><code>@..trunk()</code>: the new changes that I have added</li> 232 - <li><code>trunk()</code>: the trunk rev itself</li> 233 - <li><code>@::</code>: all descendants of <code>@</code></li> 234 - <li><code>fork_point(trunk() | @)</code>: the rev from which the two 235 - streams of work diverged</li> 236 - </ul> 237 - <p>Rougly, when working on a stack, this is what I can see by supplying 238 - the above revset expression (simplified output):</p> 239 - <div class="sourceCode" id="cb12"><pre 240 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> jj log <span class="at">-r</span> <span class="st">&#39;trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)&#39;</span></span> 241 - <span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a><span class="ex">@</span> xdihgmke <span class="op">&lt;</span>-- me</span> 242 - <span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> vurstull</span> 243 - <span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> plmznxvy</span> 244 - <span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> ihefghyy</span> 245 - <span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> <span class="pp">*</span> towvwqxk trunk <span class="op">&lt;</span>-- trunk</span> 246 - <span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> <span class="pp">*</span> ryzqnyvs</span> 247 - <span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> <span class="pp">*</span> kqvutzxr</span> 248 - <span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a><span class="ex">├─╯</span></span> 249 - <span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a><span class="ex">*</span> vwpqwmms <span class="op">&lt;</span>-- fork-point</span></code></pre></div> 250 - <p>And sometimes, when I am editing older parts of my stack 251 - (<code>@::</code> helps us grab <code>xdihgmke</code> and 252 - <code>vurstull</code>):</p> 253 - <div class="sourceCode" id="cb13"><pre 254 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> jj log <span class="at">-r</span> <span class="st">&#39;trunk()..@ | @..trunk() | trunk() | @:: | fork_point(trunk() | @)&#39;</span></span> 255 - <span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> xdihgmke</span> 256 - <span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> vurstull</span> 257 - <span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a><span class="ex">@</span> plmznxvy <span class="op">&lt;</span>-- me</span> 258 - <span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> ihefghyy</span> 259 - <span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> <span class="pp">*</span> towvwqxk trunk <span class="op">&lt;</span>-- trunk</span> 260 - <span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> <span class="pp">*</span> ryzqnyvs</span> 261 - <span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> <span class="pp">*</span> kqvutzxr</span> 262 - <span id="cb13-9"><a href="#cb13-9" aria-hidden="true" tabindex="-1"></a><span class="ex">├─╯</span></span> 263 - <span id="cb13-10"><a href="#cb13-10" aria-hidden="true" tabindex="-1"></a><span class="ex">*</span> vwpqwmms <span class="op">&lt;</span>-- fork-point</span></code></pre></div> 264 - <p>To rebase, I would run:</p> 265 - <pre><code>jj rebase -s ihefghyy -d towvwqxk</code></pre> 266 - <h3 id="aliases">Aliases</h3> 267 - <p>I imagine that most git power users are already familiar with 268 - aliases. The only alias I see myself using often is:</p> 269 - <div class="sourceCode" id="cb15"><pre 270 - class="sourceCode toml"><code class="sourceCode toml"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[aliases]</span></span> 271 - <span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a><span class="dt">tug</span> <span class="op">=</span> <span class="op">[</span><span class="st">&quot;bookmark&quot;</span><span class="op">,</span> <span class="st">&quot;move&quot;</span><span class="op">,</span> <span class="st">&quot;--from&quot;</span><span class="op">,</span> <span class="st">&quot;heads(::@- &amp; bookmarks())&quot;</span><span class="op">,</span> <span class="st">&quot;--to&quot;</span><span class="op">,</span> <span class="st">&quot;@-&quot;</span><span class="op">]</span><span class="er">;</span></span></code></pre></div> 272 - <p>In action:</p> 273 - <div class="sourceCode" id="cb16"><pre 274 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="co"># ugh my bookmark is way behind</span></span> 275 - <span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> jj log</span> 276 - <span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a><span class="ex">@</span> xdihgmke</span> 277 - <span id="cb16-4"><a href="#cb16-4" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> vurstull</span> 278 - <span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> cyzmakil</span> 279 - <span id="cb16-6"><a href="#cb16-6" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> plmznxvy</span> 280 - <span id="cb16-7"><a href="#cb16-7" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> ihefghyy some-bookmark</span> 281 - <span id="cb16-8"><a href="#cb16-8" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span></span> 282 - <span id="cb16-9"><a href="#cb16-9" aria-hidden="true" tabindex="-1"></a><span class="ex">~</span></span> 283 - <span id="cb16-10"><a href="#cb16-10" aria-hidden="true" tabindex="-1"></a></span> 284 - <span id="cb16-11"><a href="#cb16-11" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> jj tug</span> 285 - <span id="cb16-12"><a href="#cb16-12" aria-hidden="true" tabindex="-1"></a></span> 286 - <span id="cb16-13"><a href="#cb16-13" aria-hidden="true" tabindex="-1"></a><span class="co"># ready to push!</span></span> 287 - <span id="cb16-14"><a href="#cb16-14" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> jj log</span> 288 - <span id="cb16-15"><a href="#cb16-15" aria-hidden="true" tabindex="-1"></a><span class="ex">@</span> xdihgmke</span> 289 - <span id="cb16-16"><a href="#cb16-16" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> vurstull some-bookmark<span class="pp">*</span></span> 290 - <span id="cb16-17"><a href="#cb16-17" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> cyzmakil</span> 291 - <span id="cb16-18"><a href="#cb16-18" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> plmznxvy</span> 292 - <span id="cb16-19"><a href="#cb16-19" aria-hidden="true" tabindex="-1"></a><span class="ex">·</span> ihefghyy</span> 293 - <span id="cb16-20"><a href="#cb16-20" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span></span> 294 - <span id="cb16-21"><a href="#cb16-21" aria-hidden="true" tabindex="-1"></a><span class="ex">~</span></span></code></pre></div> 295 - <p>This alias was stolen from this <a 296 - href="https://gist.github.com/thoughtpolice/8f2fd36ae17cd11b8e7bd93a70e31ad6#file-jjconfig-toml">wonderful 297 - gist</a> by <a 298 - href="https://github.com/jj-vcs/jj/blob/main/docs/testimonials.md?plain=1#L120">Austin 299 - Seipp</a>.</p> 300 - <h3 id="experimental-features">Experimental features</h3> 301 - <p>I use one experimental feature, that is only available on some of the 302 - newer versions of jujutsu:</p> 303 - <div class="sourceCode" id="cb17"><pre 304 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="co"># built from master</span></span> 305 - <span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> jj version</span> 306 - <span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a><span class="ex">jj</span> 0.29.0-8c7ca30074767257d75e3842581b61e764d022cf</span></code></pre></div> 307 - <div class="sourceCode" id="cb18"><pre 308 - class="sourceCode toml"><code class="sourceCode toml"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[git]</span></span> 309 - <span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a><span class="dt">write-change-id-header</span> <span class="op">=</span> <span class="cn">true</span></span></code></pre></div> 310 - <p>This writes an extra commit-header (not to be confused with 311 - commit-trailer, which goes directly in the commit body) with the jujutsu 312 - change-id, as seen by inspecting the commit object:</p> 313 - <div class="sourceCode" id="cb19"><pre 314 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> git cat-file commit 4edebe96159bf81c3be662d0a2b4c7b08a062968</span> 315 - <span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a><span class="ex">tree</span> a9b7e9e3eed8a22c35829bf586d7560ec8396124</span> 316 - <span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a><span class="ex">parent</span> edc0d2750d4848bc05cfd3255ee1ca916bea9156</span> 317 - <span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a><span class="ex">author</span> oppiliappan <span class="op">&lt;</span>me@oppi.li<span class="op">&gt;</span> 1747919102 +0100</span> 318 - <span id="cb19-5"><a href="#cb19-5" aria-hidden="true" tabindex="-1"></a><span class="ex">committer</span> oppiliappan <span class="op">&lt;</span>me@oppi.li<span class="op">&gt;</span> 1747919102 +0100</span> 319 - <span id="cb19-6"><a href="#cb19-6" aria-hidden="true" tabindex="-1"></a><span class="ex">change-id</span> skrrxvvxlpzrqzpxlxksvryrykpxkvon <span class="op">&lt;</span>-- this</span></code></pre></div> 320 - <p>This allows code forges to extract change-ids from commits, and most 321 - crucially: <strong>allows tracking changes across force pushes</strong>. 322 - This enables the <a 323 - href="https://gist.github.com/thoughtpolice/9c45287550a56b2047c6311fbadebed2">interdiff 324 - format</a> of code-review. Stacking, commit-wise-review, and interdiff 325 - are all supported on <a href="https://tangled.sh">tangled</a> (you can 326 - read more <a 327 - href="https://bsky.app/profile/tangled.sh/post/3lptwcb47kc2u">here</a>), 328 - if you submit a branch with this experimental feature enabled.</p> 329 - <h3 id="misc">Misc</h3> 330 - <p>Couple of other quality of life additions:</p> 331 - <div class="sourceCode" id="cb20"><pre 332 - class="sourceCode toml"><code class="sourceCode toml"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="kw">[ui]</span></span> 333 - <span id="cb20-2"><a href="#cb20-2" aria-hidden="true" tabindex="-1"></a><span class="dt">default-command</span> <span class="op">=</span> <span class="st">&quot;status&quot;</span></span> 334 - <span id="cb20-3"><a href="#cb20-3" aria-hidden="true" tabindex="-1"></a><span class="dt">pager</span> <span class="op">=</span> <span class="st">&quot;delta&quot;</span></span></code></pre></div> 335 - <p>I also use nix and home-manager to configure jj, you can find my 336 - configuration <a href="https://plonk.li/r/YQ">here</a>.</p> 337 - 338 - </div> 339 - 340 - <div class="intro"> 341 - Hi. 342 - <div class="hot-links"> 343 - <a href="/index.xml" class="feed-button">Subscribe</a> 344 - </div> 345 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 346 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 347 - <p>Reach out at oppili@libera.chat.</p> 348 - </div> 349 - 350 - <a href="/" class="post-end-link">Home</a> 351 - <span>/</span> 352 - <a href="/posts" class="post-end-link">Posts</a> 353 - <span>/</span> 354 - <a class="post-end-link">Configuring Jujutsu</a> 355 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/configuring_jujutsu.md 356 - ">View Raw</a> 357 - </div> 358 - </div> 359 - </body> 360 - </html>
-322
docs/posts/curing_a_case_of_git-UX/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Curing A Case Of Git-UX"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/curing_a_case_of_git-UX"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Curing A Case Of Git-UX · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Curing A Case Of Git-UX</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/curing_a_case_of_git-UX.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 03/09 — 2022 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 127.87 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 9.6 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Curing A Case Of Git-UX 43 - </h1> 44 - <div class="post-text"> 45 - <p>Git worktrees are great, but they fall behind the venerable 46 - <code>git checkout</code> sometimes. I attempted to fix that with <a 47 - href="https://github.com/junegunn/fzf">fzf</a> and a bit of bash.</p> 48 - <p><a href="https://asciinema.org/a/D297ztKRzpE4gAHbPTPmkqYps"><img 49 - src="https://asciinema.org/a/D297ztKRzpE4gAHbPTPmkqYps.svg" /></a></p> 50 - <p>Fear not if you haven’t heard of “worktrees”, I have included a 51 - primer here.<br /> 52 - <a href="#what-makes-them-clunky">Skip the primer -&gt;</a>.</p> 53 - <h3 id="why-worktrees">Why Worktrees?</h3> 54 - <p>Picture this. You are whacking away on a feature branch. Halfway 55 - there, in fact. Your friend asks you fix something urgently. You proceed 56 - to do one of three things:</p> 57 - <ul> 58 - <li>create a temporary branch, make a WIP commit, begin working on the 59 - fix</li> 60 - <li>stash away your changes, begin working on the fix</li> 61 - <li>unfriend said friend for disturbing your flow</li> 62 - </ul> 63 - <p>All of these options are … subpar. With the temporary branch, you are 64 - forced to create a partial, non-working commit, and then reset said 65 - commit once done with the fix. With the stash approach, you are required 66 - to now keep a mental model of the stash, be aware of untracked files 67 - that don’t get stashed by default, etc. Why won’t git just let you work 68 - on two things at the same time without <em>thinking</em> so much?</p> 69 - <p>That is exactly what worktrees let you do. Worktrees let you have 70 - more than one checkout at a time, each checkout in a separate directory. 71 - Like creating a new clone, but safer (it disallows checking out the same 72 - branch twice) and a lot more space efficient (the new working tree is 73 - “linked” to the “main” worktree, and a good amount of stuff is shared). 74 - When your friend asks you to make the fix, you proceed like so:</p> 75 - <ol type="1"> 76 - <li>Create a new working tree with:</li> 77 - </ol> 78 - <div class="sourceCode" id="cb1"><pre 79 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co"># git worktree add -b &lt;branch-name&gt; &lt;path&gt; &lt;from&gt;</span></span> 80 - <span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="fu">git</span> worktree add <span class="at">-b</span> fix-stuff /path/to/tree master</span></code></pre></div> 81 - <ol start="2" type="1"> 82 - <li><code>cd</code> into <code>/path/to/tree</code></li> 83 - <li>Fix, test, commit, push, party</li> 84 - <li>Go back to your work, <code>cd -</code></li> 85 - </ol> 86 - <p>Easy as cake. You didn’t have to settle for a partially working 87 - commit, you didn’t to deal with this “stash” thing, <em>and</em> you 88 - didn’t have to unfriend your friend. Treating each branch as a directory 89 - just <em>feels</em> more intuitive, more UNIX-y.</p> 90 - <p>A few weeks later, you find yourself singing in praise of worktrees, 91 - working on several things simultaneously. And at the same time, cursing 92 - them for being a little … clunky.</p> 93 - <h3 id="what-makes-them-clunky">What makes them clunky?</h3> 94 - <p>Worktrees are great at what they claim to do. They stay out of the 95 - way when you need a checkout posthaste. However, as you start using them 96 - regularly, you realize they are not as flexible as 97 - <code>git checkout</code> or <code>git switch</code>.</p> 98 - <h4 id="branch-hopping">Branch-hopping</h4> 99 - <p>You can <code>git checkout &lt;branch&gt;</code> from anywhere within 100 - a git repository. You can’t “jump” to a worktree in the same fashion. 101 - The closest you can get, is to run <code>git worktree list</code>, copy 102 - the path corresponding to your branch, and <code>cd</code> into it.</p> 103 - <p>Branch-hopping with the good ol’ git-checkout:</p> 104 - <div class="sourceCode" id="cb2"><pre 105 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co"># anywhere, anytime</span></span> 106 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> git checkout feature/is-ascii-octdigit</span></code></pre></div> 107 - <p>Meanwhile, in worktree world:</p> 108 - <div class="sourceCode" id="cb3"><pre 109 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co"># keeping these paths in your head is hard</span></span> 110 - <span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> git worktree list</span> 111 - <span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="ex">~/worktrees/rustc/master</span> eac6c33bc63 <span class="pp">[</span><span class="ss">master</span><span class="pp">]</span></span> 112 - <span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="ex">~/worktrees/rustc/improve-std-char-docs</span> 94cba88553e <span class="pp">[</span><span class="ss">improve</span><span class="pp">-</span><span class="ss">std</span><span class="pp">-</span><span class="ss">char</span><span class="pp">-</span><span class="ss">docs</span><span class="pp">]</span></span> 113 - <span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="ex">~/worktrees/rustc/is-ascii-octdigit</span> bc57be3af7a <span class="pp">[</span><span class="ss">feature/is</span><span class="pp">-</span><span class="ss">ascii</span><span class="pp">-</span><span class="ss">octdigit</span><span class="pp">]</span></span> 114 - <span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="ex">~/my/other/path/oh/god</span> op57or3ns7n <span class="pp">[</span><span class="ss">fix/some</span><span class="pp">-</span><span class="ss">error</span><span class="pp">]</span></span> 115 - <span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a></span> 116 - <span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> cd ~/worktrees/rustc/is-ascii-octdigit</span></code></pre></div> 117 - <h4 id="branch-previewing">Branch-previewing</h4> 118 - <p>You can “preview” branches with <code>git branch -v</code>. However, 119 - to get an idea of what “recent activity” on a worktree looks like, you 120 - might need some juggling. You can’t glean much info about a worktree in 121 - a jiffy.</p> 122 - <p>Branch-previewing with the good ol’ git-branch:</p> 123 - <div class="sourceCode" id="cb4"><pre 124 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> git branch <span class="at">-v</span></span> 125 - <span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="ex">+</span> feature/is-ascii-octdigit bc57be3af7a introduce {char, u8}::is_ ...</span> 126 - <span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="ex">+</span> improve-std-char-docs 94cba88553e add whitespace in assert ...</span> 127 - <span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="ex">*</span> master eac6c33bc63 Auto merge of <span class="co">#100869 - n ...</span></span></code></pre></div> 128 - <p>Meanwhile in worktree wonderland:</p> 129 - <pre><code>λ git worktree list 130 - ~/worktrees/rustc/master eac6c33bc63 [master] 131 - ~/worktrees/rustc/improve-std-char-docs 94cba88553e [improve-std-char-docs] 132 - ~/worktrees/rustc/is-ascii-octdigit bc57be3af7a [feature/is-ascii-octdigit] 133 - 134 - # aha, so ../is-ascii-octdigit corresponds to `feature/is-ascii-octdigit` 135 - λ git log feature/is-ascii-octdigit 136 - bc57be3af7a introduce {char, u8}::is_ascii_octdigit 137 - eac6c33bc63 Auto merge of #100869 - nnethercote:repl ... 138 - b32223fec10 Auto merge of #100707 - dzvon:fix-typo, ... 139 - aa857eb953e Auto merge of #100537 - petrochenkov:pic ... 140 - 141 - # extra work to make the branch &lt;-&gt; worktree correspondence</code></pre> 142 - <h4 id="shell-completions">Shell completions</h4> 143 - <p>Lastly, you can bank on shell completions to fill in your branch 144 - whilst using <code>git checkout</code>. Worktrees have no such 145 - conveniences.</p> 146 - <p>We can mend these minor faults with fzf.</p> 147 - <h3 id="unclunkifying-worktrees">Unclunkifying worktrees</h3> 148 - <p>I’d suggest looking up <a 149 - href="https://github.com/junegunn/fzf">fzf</a> (or <a 150 - href="https://github.com/lotabout/skim">skim</a> or <a 151 - href="https://github.com/jhawthorn/fzy">fzy</a>). These things make it 152 - cake-easy to add interactivity to your shell. Onto fixing the first 153 - minor fault, the inability to “jump” to a worktree from anywhere within 154 - a git repository.</p> 155 - <p>I have a little function called <code>gwj</code> which stands for 156 - “git worktree jump”. The idea is to list all the worktrees, select one 157 - with fzf, and <code>cd</code> to it upon selection:</p> 158 - <div class="sourceCode" id="cb6"><pre 159 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="fu">gwj ()</span> <span class="kw">{</span></span> 160 - <span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a> <span class="bu">local</span> <span class="va">out</span></span> 161 - <span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a> <span class="va">out</span><span class="op">=</span><span class="va">$(</span><span class="fu">git</span> worktree list <span class="kw">|</span> <span class="ex">fzf</span> <span class="kw">|</span> <span class="fu">awk</span> <span class="st">&#39;{print $1}&#39;</span><span class="va">)</span></span> 162 - <span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a> <span class="bu">cd</span> <span class="va">$out</span></span> 163 - <span id="cb6-5"><a href="#cb6-5" aria-hidden="true" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div> 164 - <p>That is all of it really. Head into a git repository:</p> 165 - <div class="sourceCode" id="cb7"><pre 166 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="co"># here, &quot;master&quot; is a directory, which contains my main</span></span> 167 - <span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="co"># worktree: a checkout of the master branch on rust-lang/rust </span></span> 168 - <span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> cd ~/worktrees/rustc/master/library/core/src</span> 169 - <span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> <span class="co"># hack away</span></span></code></pre></div> 170 - <p>Preferably one with a few worktrees:</p> 171 - <div class="sourceCode" id="cb8"><pre 172 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> git worktree list</span> 173 - <span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="ex">~/worktrees/rustc/master</span> eac6c33bc63 <span class="pp">[</span><span class="ss">master</span><span class="pp">]</span></span> 174 - <span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="ex">~/worktrees/rustc/improve-std-char-docs</span> 94cba88553e <span class="pp">[</span><span class="ss">improve</span><span class="pp">-</span><span class="ss">std</span><span class="pp">-</span><span class="ss">char</span><span class="pp">-</span><span class="ss">docs</span><span class="pp">]</span></span> 175 - <span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a><span class="ex">~/worktrees/rustc/is-ascii-octdigit</span> bc57be3af7a <span class="pp">[</span><span class="ss">feature/is</span><span class="pp">-</span><span class="ss">ascii</span><span class="pp">-</span><span class="ss">octdigit</span><span class="pp">]</span></span></code></pre></div> 176 - <p>And hit <code>gwj</code> (pretend that the pipe, |, is your 177 - cursor):</p> 178 - <div class="sourceCode" id="cb9"><pre 179 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> gwj</span> 180 - <span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> <span class="kw">|</span></span> 181 - <span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a> <span class="ex">4/4</span></span> 182 - <span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> ~/worktrees/rustc/master <span class="ex">eac6c33bc63</span> <span class="pp">[</span><span class="ss">master</span><span class="pp">]</span></span> 183 - <span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">~/worktrees/rustc/improve-std-char-docs</span> 94cba88553e <span class="pp">[</span><span class="ss">improve</span><span class="pp">-</span><span class="ss">std</span><span class="pp">-</span><span class="ss">char</span><span class="pp">-</span><span class="ss">docs</span><span class="pp">]</span></span> 184 - <span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">~/worktrees/rustc/is-ascii-octdigit</span> bc57be3af7a <span class="pp">[</span><span class="ss">feature/is</span><span class="pp">-</span><span class="ss">ascii</span><span class="pp">-</span><span class="ss">octdigit</span><span class="pp">]</span></span></code></pre></div> 185 - <p>Approximately type in your branch of choice:</p> 186 - <div class="sourceCode" id="cb10"><pre 187 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> gwj</span> 188 - <span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> docs<span class="kw">|</span></span> 189 - <span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a> <span class="ex">4/4</span></span> 190 - <span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> ~/worktrees/rustc/improve-std-char-docs <span class="ex">94cba88553e</span> <span class="pp">[</span><span class="ss">improve</span><span class="pp">-</span><span class="ss">std</span><span class="pp">-</span><span class="ss">char</span><span class="pp">-</span><span class="ss">docs</span><span class="pp">]</span></span></code></pre></div> 191 - <p>And hit enter. You should find yourself in the selected worktree.</p> 192 - <p>Onward, to the next fault, lack of preview-bility. We can utilize 193 - fzf’s aptly named <code>--preview</code> flag, to, well, preview our 194 - worktree before performing a selection:</p> 195 - <div class="sourceCode" id="cb11"><pre 196 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="fu">gwj ()</span> <span class="kw">{</span></span> 197 - <span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a> <span class="bu">local</span> <span class="va">out</span></span> 198 - <span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a> <span class="va">out</span><span class="op">=</span><span class="va">$(</span></span> 199 - <span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a> <span class="fu">git</span> worktree list <span class="kw">|</span></span> 200 - <span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">fzf</span> <span class="at">--preview</span><span class="op">=</span><span class="st">&#39;git log --oneline -n10 {2}&#39;</span> <span class="kw">|</span></span> 201 - <span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a> <span class="fu">awk</span> <span class="st">&#39;{print $1}&#39;</span></span> 202 - <span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a> <span class="va">)</span></span> 203 - <span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a> <span class="bu">cd</span> <span class="va">$out</span></span> 204 - <span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div> 205 - <p>Once again, hit <code>gwj</code> inside a git repository with linked 206 - worktrees:</p> 207 - <div class="sourceCode" id="cb12"><pre 208 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> gwj</span> 209 - <span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a><span class="ex">╭─────────────────────────────────────────────────────────╮</span></span> 210 - <span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> eac6c33bc63 Auto merge of 100869 nnethercote:replace... │</span> 211 - <span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> b32223fec10 Auto merge of 100707 dzvon:fix-typo, r=d... │</span> 212 - <span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> aa857eb953e Auto merge of 100537 petrochenkov:picche... │</span> 213 - <span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> 3892b7074da Auto merge of 100210 mystor:proc_macro_d... │</span> 214 - <span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> db00199d999 Auto merge of 101249 matthiaskrgr:rollup... │</span> 215 - <span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> 14d216d33ba Rollup merge of 101240 JohnTitor:JohnTit... │</span> 216 - <span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> 3da66f03531 Rollup merge of 101236 thomcc:winfs-noze... │</span> 217 - <span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> 0620f6e90af Rollup merge of 101230 davidtwco:transla... │</span> 218 - <span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> c30c42ee299 Rollup merge of 101229 mgeisler:link-try... │</span> 219 - <span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> e5356712b9e Rollup merge of 101165 ldm0:drain_to_ite... │</span> 220 - <span id="cb12-13"><a href="#cb12-13" aria-hidden="true" tabindex="-1"></a><span class="ex">╰─────────────────────────────────────────────────────────╯</span></span> 221 - <span id="cb12-14"><a href="#cb12-14" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span></span> 222 - <span id="cb12-15"><a href="#cb12-15" aria-hidden="true" tabindex="-1"></a> <span class="ex">4/4</span></span> 223 - <span id="cb12-16"><a href="#cb12-16" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> /home/np/worktrees/compiler/master <span class="ex">eac6c...</span></span> 224 - <span id="cb12-17"><a href="#cb12-17" aria-hidden="true" tabindex="-1"></a> <span class="ex">/home/np/worktrees/compiler/improve-std-char-docs</span> 94cba...</span> 225 - <span id="cb12-18"><a href="#cb12-18" aria-hidden="true" tabindex="-1"></a> <span class="ex">/home/np/worktrees/compiler/is-ascii-octdigit</span> bc57b...</span></code></pre></div> 226 - <p>A fancy preview of the last 10 commits on the branch that the 227 - selected worktree corresponds to. In other words, sight for sore eyes. 228 - Our little script is already shaping up to be useful, you hit 229 - <code>gwj</code>, browse through your worktrees, preview each one and 230 - automatically <code>cd</code> to your selection. But we are not done 231 - yet.</p> 232 - <p>The last fault was lack shell completions. A quick review of what a 233 - shell completion really does:</p> 234 - <div class="sourceCode" id="cb13"><pre 235 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> git checkout f<span class="op">&lt;</span>tab<span class="op">&gt;</span></span> 236 - <span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a><span class="ex">feature/is-ascii-octdigit</span></span> 237 - <span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a><span class="ex">fix/some-error</span></span> 238 - <span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a><span class="ex">format-doc-tests</span></span> 239 - <span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a></span> 240 - <span id="cb13-6"><a href="#cb13-6" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> git checkout feat<span class="op">&lt;</span>tab<span class="op">&gt;</span></span> 241 - <span id="cb13-7"><a href="#cb13-7" aria-hidden="true" tabindex="-1"></a></span> 242 - <span id="cb13-8"><a href="#cb13-8" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> git checkout feature/is-ascii-octdigit</span></code></pre></div> 243 - <p>Each time you hit “tab”, the shell produces a few “completion 244 - candidates”, and once you have just a single candidate left, the shell 245 - inserts that for you directly into your edit line. Of course, this 246 - process varies from shell to shell.</p> 247 - <p>fzf narrows down your options as you type into the prompt, but you 248 - still have to:</p> 249 - <ol type="1"> 250 - <li>Type <code>gwj</code></li> 251 - <li>Hit enter</li> 252 - <li>Type out a query and narrow down your search</li> 253 - <li>Hit enter</li> 254 - </ol> 255 - <p>We can speed that up a bit, have fzf narrow down the candidates on 256 - startup, just like our shell does:</p> 257 - <div class="sourceCode" id="cb14"><pre 258 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a><span class="fu">gwj ()</span> <span class="kw">{</span></span> 259 - <span id="cb14-2"><a href="#cb14-2" aria-hidden="true" tabindex="-1"></a> <span class="bu">local</span> <span class="va">out</span> <span class="va">query</span></span> 260 - <span id="cb14-3"><a href="#cb14-3" aria-hidden="true" tabindex="-1"></a> <span class="va">query</span><span class="op">=</span><span class="st">&quot;</span><span class="va">${1</span><span class="op">:-</span> <span class="va">}</span><span class="st">&quot;</span></span> 261 - <span id="cb14-4"><a href="#cb14-4" aria-hidden="true" tabindex="-1"></a> <span class="va">out</span><span class="op">=</span><span class="va">$(</span></span> 262 - <span id="cb14-5"><a href="#cb14-5" aria-hidden="true" tabindex="-1"></a> <span class="fu">git</span> worktree list <span class="kw">|</span></span> 263 - <span id="cb14-6"><a href="#cb14-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">fzf</span> <span class="at">--preview</span><span class="op">=</span><span class="st">&#39;git log --oneline -n10 {2}&#39;</span> <span class="at">--query</span> <span class="st">&quot;</span><span class="va">$query</span><span class="st">&quot;</span> <span class="at">-1</span> <span class="kw">|</span></span> 264 - <span id="cb14-7"><a href="#cb14-7" aria-hidden="true" tabindex="-1"></a> <span class="fu">awk</span> <span class="st">&#39;{print $1}&#39;</span></span> 265 - <span id="cb14-8"><a href="#cb14-8" aria-hidden="true" tabindex="-1"></a> <span class="va">)</span></span> 266 - <span id="cb14-9"><a href="#cb14-9" aria-hidden="true" tabindex="-1"></a> <span class="bu">cd</span> <span class="va">$out</span></span> 267 - <span id="cb14-10"><a href="#cb14-10" aria-hidden="true" tabindex="-1"></a><span class="kw">}</span></span></code></pre></div> 268 - <p>The change is extremely tiny, blink-and-you’ll-miss-it kinda tiny. We 269 - added a little <code>--query</code> flag, that allows you to prefill the 270 - prompt, and the <code>-1</code> flag, that avoids the interactive finder 271 - if only one match exists on startup:</p> 272 - <div class="sourceCode" id="cb15"><pre 273 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a><span class="co"># skip through the fzf prompt:</span></span> 274 - <span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> gwj master</span> 275 - <span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a><span class="co"># cd -- ~/worktrees/rustc/master</span></span> 276 - <span id="cb15-4"><a href="#cb15-4" aria-hidden="true" tabindex="-1"></a></span> 277 - <span id="cb15-5"><a href="#cb15-5" aria-hidden="true" tabindex="-1"></a><span class="co"># more than one option, we end up in the interactive finder</span></span> 278 - <span id="cb15-6"><a href="#cb15-6" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> gwj improve</span> 279 - <span id="cb15-7"><a href="#cb15-7" aria-hidden="true" tabindex="-1"></a><span class="ex">╭─────────────────────────────────────────────────────────╮</span></span> 280 - <span id="cb15-8"><a href="#cb15-8" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> eac6c33bc63 Auto merge of 100869 nnethercote:replace... │</span> 281 - <span id="cb15-9"><a href="#cb15-9" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> b32223fec10 Auto merge of 100707 dzvon:fix-typo, r=d... │</span> 282 - <span id="cb15-10"><a href="#cb15-10" aria-hidden="true" tabindex="-1"></a><span class="ex">│</span> aa857eb953e Auto merge of 100537 petrochenkov:picche... │</span> 283 - <span id="cb15-11"><a href="#cb15-11" aria-hidden="true" tabindex="-1"></a><span class="ex">╰─────────────────────────────────────────────────────────╯</span></span> 284 - <span id="cb15-12"><a href="#cb15-12" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> improve</span> 285 - <span id="cb15-13"><a href="#cb15-13" aria-hidden="true" tabindex="-1"></a> <span class="ex">2/2</span></span> 286 - <span id="cb15-14"><a href="#cb15-14" aria-hidden="true" tabindex="-1"></a><span class="op">&gt;</span> /home/np/worktrees/compiler/improve-const-perf <span class="ex">eac6c...</span></span> 287 - <span id="cb15-15"><a href="#cb15-15" aria-hidden="true" tabindex="-1"></a> <span class="ex">/home/np/worktrees/compiler/improve-std-char-docs</span> 94cba...</span></code></pre></div> 288 - <p>Throw some error handling in there, hook up a similar script to 289 - improve the UX of <code>git worktree remove</code>, go wild. A few more 290 - helpers I’ve got:</p> 291 - <div class="sourceCode" id="cb16"><pre 292 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a><span class="co"># gwa /path/to/branch-name</span></span> 293 - <span id="cb16-2"><a href="#cb16-2" aria-hidden="true" tabindex="-1"></a><span class="co"># creates a new branch and &quot;switches&quot; to it</span></span> 294 - <span id="cb16-3"><a href="#cb16-3" aria-hidden="true" tabindex="-1"></a><span class="kw">function</span><span class="fu"> gwa ()</span> <span class="kw">{</span></span> 295 - <span id="cb16-4"><a href="#cb16-4" aria-hidden="true" tabindex="-1"></a> <span class="fu">git</span> worktree add <span class="st">&quot;</span><span class="va">$1</span><span class="st">&quot;</span> <span class="kw">&amp;&amp;</span> <span class="bu">cd</span> <span class="st">&quot;</span><span class="va">$1</span><span class="st">&quot;</span></span> 296 - <span id="cb16-5"><a href="#cb16-5" aria-hidden="true" tabindex="-1"></a><span class="kw">}</span></span> 297 - <span id="cb16-6"><a href="#cb16-6" aria-hidden="true" tabindex="-1"></a></span> 298 - <span id="cb16-7"><a href="#cb16-7" aria-hidden="true" tabindex="-1"></a><span class="bu">alias</span> gwls=<span class="st">&quot;git worktree list&quot;</span></span></code></pre></div> 299 - 300 - </div> 301 - 302 - <div class="intro"> 303 - Hi. 304 - <div class="hot-links"> 305 - <a href="/index.xml" class="feed-button">Subscribe</a> 306 - </div> 307 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 308 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 309 - <p>Reach out at oppili@libera.chat.</p> 310 - </div> 311 - 312 - <a href="/" class="post-end-link">Home</a> 313 - <span>/</span> 314 - <a href="/posts" class="post-end-link">Posts</a> 315 - <span>/</span> 316 - <a class="post-end-link">Curing A Case Of Git-UX</a> 317 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/curing_a_case_of_git-UX.md 318 - ">View Raw</a> 319 - </div> 320 - </div> 321 - </body> 322 - </html>
-139
docs/posts/font_size_fallacies/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Font Size Fallacies"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/font_size_fallacies"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Font Size Fallacies · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Font Size Fallacies</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/font_size_fallacies.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 16/03 — 2020 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 32.37 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 3.3 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Font Size Fallacies 43 - </h1> 44 - <div class="post-text"> 45 - <p>I am not an expert with fonts, but I do have some experience <a 46 - href="#fn1" class="footnote-ref" id="fnref1" 47 - role="doc-noteref"><sup>1</sup></a>, and common sense. This post aims to 48 - debunk some misconceptions about font sizes!</p> 49 - <p>11 px on your display is <em>probably not</em> 11 px on my display. 50 - Let’s do some quick math. I have two displays, 1366x768 @ 21” and 51 - another with 1920x1080 @ 13”, call them <code>A</code> and 52 - <code>B</code> for now.</p> 53 - <p>Display <code>A</code> has 1,049,088 pixels. A pixel is a square, of 54 - side say, <code>s</code> cm. The total area covered by my 21” display is 55 - about 1,066 cm^2 (41x26). Thus,</p> 56 - <pre><code>Display A 57 - Dimensions: 1366x768 @ 21&quot; (41x26 sq. cm) 58 - 1,049,088 s^2 = 1066 59 - s = 0.0318 cm (side of a pixel on Display A)</code></pre> 60 - <p>Bear with me, as I repeat the number crunching for Display 61 - <code>B</code>:</p> 62 - <pre><code>Display B 63 - Dimensions: 1920x1080 @ 13&quot; (29.5x16.5 sq. cm) 64 - 2,073,600 s^2 = 486.75 65 - s = 0.0153 cm (side of a pixel on Display B)</code></pre> 66 - <p>The width of a pixel on Display <code>A</code> is <em>double</em> the 67 - width of a pixel on Display <code>B</code>. The area occupied by a pixel 68 - on Display <code>A</code> is <em>4 times</em> the area occupied by a 69 - pixel on Display <code>B</code>.</p> 70 - <p><em>The size of a pixel varies from display to display!</em></p> 71 - <p>A 5x11 bitmap font on Display <code>A</code> would be around 4 mm 72 - tall whereas the same bitmap font on Display <code>B</code> would be 73 - around 1.9 mm tall. A 11 px tall character on <code>B</code> is visually 74 - equivalent to a 5 px character on <code>A</code>. When you view a 75 - screenshot of Display <code>A</code> on Display <code>B</code>, the 76 - contents are shrunk down by a factor of 2!</p> 77 - <p>So screen resolution is not enough, how else do we measure size? 78 - Pixel Density! Keen readers will realize that the 5^th grade math 79 - problem we solved up there showcases pixel density, or, pixels per cm 80 - (PPCM). Usually we deal with pixels per inch (PPI).</p> 81 - <p><strong>Note:</strong> PPI is not to be confused with DPI <a 82 - href="#fn2" class="footnote-ref" id="fnref2" 83 - role="doc-noteref"><sup>2</sup></a> (dots per inch). DPI is defined for 84 - printers.</p> 85 - <p>In our example, <code>A</code> is a 75 ppi display and <code>B</code> 86 - is around 165 ppi <a href="#fn3" class="footnote-ref" id="fnref3" 87 - role="doc-noteref"><sup>3</sup></a>. A low ppi display appears to be 88 - ‘pixelated’, because the pixels are more prominent, much like Display 89 - <code>A</code>. A higher ppi usually means you can view larger images 90 - and render crispier fonts. The average desktop display can stuff 100-200 91 - pixels per inch. Smart phones usually fall into the 400-600 ppi 92 - (XXXHDPI) category. The human eye fails to differentiate detail past 300 93 - ppi.</p> 94 - <p><em>So … streaming an 8K video on a 60” TV provides the same clarity 95 - as a HD video on a smart phone?</em></p> 96 - <p>Absolutely. Well, clarity is subjective, but the amount of detail you 97 - can discern on mobile displays has always been limited. Salty consumers 98 - of the Xperia 1 <a href="#fn4" class="footnote-ref" id="fnref4" 99 - role="doc-noteref"><sup>4</sup></a> will say otherwise.</p> 100 - <p>Maybe I will talk about font rendering in another post, but thats all 101 - for now. Don’t judge a font size by its screenshot.</p> 102 - <section id="footnotes" class="footnotes footnotes-end-of-document" 103 - role="doc-endnotes"> 104 - <hr /> 105 - <ol> 106 - <li id="fn1"><p>https://github.com/nerdypepper/scientifica<a 107 - href="#fnref1" class="footnote-back" role="doc-backlink">↩︎</a></p></li> 108 - <li id="fn2"><p>https://en.wikipedia.org/wiki/Dots_per_inch<a 109 - href="#fnref2" class="footnote-back" role="doc-backlink">↩︎</a></p></li> 110 - <li id="fn3"><p>https://www.sven.de/dpi/<a href="#fnref3" 111 - class="footnote-back" role="doc-backlink">↩︎</a></p></li> 112 - <li id="fn4"><p>https://en.wikipedia.org/wiki/Sony_Xperia_1<a 113 - href="#fnref4" class="footnote-back" role="doc-backlink">↩︎</a></p></li> 114 - </ol> 115 - </section> 116 - 117 - </div> 118 - 119 - <div class="intro"> 120 - Hi. 121 - <div class="hot-links"> 122 - <a href="/index.xml" class="feed-button">Subscribe</a> 123 - </div> 124 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 125 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 126 - <p>Reach out at oppili@libera.chat.</p> 127 - </div> 128 - 129 - <a href="/" class="post-end-link">Home</a> 130 - <span>/</span> 131 - <a href="/posts" class="post-end-link">Posts</a> 132 - <span>/</span> 133 - <a class="post-end-link">Font Size Fallacies</a> 134 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/font_size_fallacies.md 135 - ">View Raw</a> 136 - </div> 137 - </div> 138 - </body> 139 - </html>
-86
docs/posts/get_better_at_yanking_and_putting_in_vim/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Get Better At Yanking And Putting In Vim"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/get_better_at_yanking_and_putting_in_vim"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Get Better At Yanking And Putting In Vim · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Get Better At Yanking And Putting In Vim</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/get_better_at_yanking_and_putting_in_vim.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 30/07 — 2019 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 10.79 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 0.9 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Get Better At Yanking And Putting In Vim 43 - </h1> 44 - <div class="post-text"> 45 - <p>a couple of nifty tricks to help you copy-paste better:</p> 46 - <ol type="1"> 47 - <li><p>reselecting previously selected text (i use this to fix botched 48 - selections):</p> 49 - <pre><code>gv &quot; :h gv for more 50 - &quot; you can use `o` in visual mode to go to the `Other` end of the selection 51 - &quot; use a motion to fix the selection</code></pre></li> 52 - <li><p>reselecting previously yanked text:</p> 53 - <pre><code>`[v`] 54 - `[ &quot; marks the beginning of the previously yanked text :h `[ 55 - `] &quot; marks the end :h `] 56 - v &quot; visual select everything in between 57 - 58 - nnoremap gb `[v`] &quot; &quot;a quick map to perform the above</code></pre></li> 59 - <li><p>pasting and indenting text (in one go):</p> 60 - <pre><code>]p &quot; put (p) and adjust indent to current line 61 - ]P &quot; put the text before the cursor (P) and adjust indent to current line</code></pre></li> 62 - </ol> 63 - 64 - </div> 65 - 66 - <div class="intro"> 67 - Hi. 68 - <div class="hot-links"> 69 - <a href="/index.xml" class="feed-button">Subscribe</a> 70 - </div> 71 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 72 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 73 - <p>Reach out at oppili@libera.chat.</p> 74 - </div> 75 - 76 - <a href="/" class="post-end-link">Home</a> 77 - <span>/</span> 78 - <a href="/posts" class="post-end-link">Posts</a> 79 - <span>/</span> 80 - <a class="post-end-link">Get Better At Yanking And Putting In Vim</a> 81 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/get_better_at_yanking_and_putting_in_vim.md 82 - ">View Raw</a> 83 - </div> 84 - </div> 85 - </body> 86 - </html>
-210
docs/posts/gripes_with_go/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Gripes With Go"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/gripes_with_go"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Gripes With Go · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Gripes With Go</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/gripes_with_go.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 01/08 — 2020 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 76.72 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 4.9 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Gripes With Go 43 - </h1> 44 - <div class="post-text"> 45 - <p>You’ve read a lot of posts about the shortcomings of the Go 46 - programming language, so what’s one more.</p> 47 - <ol type="1"> 48 - <li><a href="#lack-of-sum-types">Lack of sum types</a></li> 49 - <li><a href="#type-assertions">Type assertions</a></li> 50 - <li><a href="#date-and-time">Date and Time</a></li> 51 - <li><a href="#statements-over-expressions">Statements over 52 - Expressions</a></li> 53 - <li><a href="#erroring-out-on-unused-variables">Erroring out on unused 54 - variables</a></li> 55 - <li><a href="#error-handling">Error handling</a></li> 56 - </ol> 57 - <h3 id="lack-of-sum-types">Lack of Sum types</h3> 58 - <p>A “Sum” type is a data type that can hold one of many states at a 59 - given time, similar to how a boolean can hold a true or a false, not too 60 - different from an <code>enum</code> type in C. Go lacks 61 - <code>enum</code> types unfortunately, and you are forced to resort to 62 - crafting your own substitute.</p> 63 - <p>A type to represent gender for example:</p> 64 - <div class="sourceCode" id="cb1"><pre class="sourceCode go"><code class="sourceCode go"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">type</span> Gender <span class="dt">int</span></span> 65 - <span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a></span> 66 - <span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> <span class="op">(</span></span> 67 - <span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> Male Gender <span class="op">=</span> <span class="ot">iota</span> <span class="co">// assigns Male to 0</span></span> 68 - <span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> Female <span class="co">// assigns Female to 1</span></span> 69 - <span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> Other <span class="co">// assigns Other to 2</span></span> 70 - <span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="op">)</span></span> 71 - <span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a></span> 72 - <span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a>fmt<span class="op">.</span>Println<span class="op">(</span><span class="st">&quot;My gender is &quot;</span><span class="op">,</span> Male<span class="op">)</span></span> 73 - <span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="co">// My gender is 0</span></span> 74 - <span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="co">// Oops! We have to implement String() for Gender ...</span></span> 75 - <span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a></span> 76 - <span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a><span class="kw">func</span> <span class="op">(</span>g Gender<span class="op">)</span> String<span class="op">()</span> <span class="dt">string</span> <span class="op">{</span></span> 77 - <span id="cb1-14"><a href="#cb1-14" aria-hidden="true" tabindex="-1"></a> <span class="cf">switch</span> <span class="op">(</span>g<span class="op">)</span> <span class="op">{</span></span> 78 - <span id="cb1-15"><a href="#cb1-15" aria-hidden="true" tabindex="-1"></a> <span class="cf">case</span> <span class="dv">0</span><span class="op">:</span> <span class="cf">return</span> <span class="st">&quot;Male&quot;</span></span> 79 - <span id="cb1-16"><a href="#cb1-16" aria-hidden="true" tabindex="-1"></a> <span class="cf">case</span> <span class="dv">1</span><span class="op">:</span> <span class="cf">return</span> <span class="st">&quot;Female&quot;</span></span> 80 - <span id="cb1-17"><a href="#cb1-17" aria-hidden="true" tabindex="-1"></a> <span class="cf">default</span><span class="op">:</span> <span class="cf">return</span> <span class="st">&quot;Other&quot;</span></span> 81 - <span id="cb1-18"><a href="#cb1-18" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 82 - <span id="cb1-19"><a href="#cb1-19" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span> 83 - <span id="cb1-20"><a href="#cb1-20" aria-hidden="true" tabindex="-1"></a></span> 84 - <span id="cb1-21"><a href="#cb1-21" aria-hidden="true" tabindex="-1"></a><span class="co">// You can accidentally do stupid stuff like:</span></span> 85 - <span id="cb1-22"><a href="#cb1-22" aria-hidden="true" tabindex="-1"></a>gender <span class="op">:=</span> Male <span class="op">+</span> <span class="dv">1</span></span></code></pre></div> 86 - <p>The Haskell equivalent of the same:</p> 87 - <div class="sourceCode" id="cb2"><pre 88 - class="sourceCode haskell"><code class="sourceCode haskell"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="kw">data</span> <span class="dt">Gender</span> <span class="ot">=</span> <span class="dt">Male</span></span> 89 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> <span class="op">|</span> <span class="dt">Female</span></span> 90 - <span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a> <span class="op">|</span> <span class="dt">Other</span></span> 91 - <span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">deriving</span> (<span class="dt">Show</span>)</span> 92 - <span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a></span> 93 - <span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="fu">print</span> <span class="op">$</span> <span class="st">&quot;My gender is &quot;</span> <span class="op">++</span> (<span class="fu">show</span> <span class="dt">Male</span>)</span></code></pre></div> 94 - <h3 id="type-assertions">Type Assertions</h3> 95 - <p>A downcast with an optional error check? What could go wrong?</p> 96 - <p>Type assertions in Go allow you to do:</p> 97 - <div class="sourceCode" id="cb3"><pre class="sourceCode go"><code class="sourceCode go"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="kw">var</span> x <span class="kw">interface</span><span class="op">{}</span> <span class="op">=</span> <span class="dv">7</span></span> 98 - <span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a>y<span class="op">,</span> goodToGo <span class="op">:=</span> x<span class="op">.(</span><span class="dt">int</span><span class="op">)</span></span> 99 - <span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> goodToGo <span class="op">{</span></span> 100 - <span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> fmt<span class="op">.</span>Println<span class="op">(</span>y<span class="op">)</span></span> 101 - <span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 102 - <p>The error check however is optional:</p> 103 - <div class="sourceCode" id="cb4"><pre class="sourceCode go"><code class="sourceCode go"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="kw">var</span> x <span class="kw">interface</span><span class="op">{}</span> <span class="op">=</span> <span class="dv">7</span></span> 104 - <span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="kw">var</span> y <span class="op">:=</span> x<span class="op">.(</span><span class="dt">float64</span><span class="op">)</span></span> 105 - <span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a>fmt<span class="op">.</span>Println<span class="op">(</span>y<span class="op">)</span></span> 106 - <span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="co">// results in a runtime error:</span></span> 107 - <span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a><span class="co">// panic: interface conversion: interface {} is int, not float64</span></span></code></pre></div> 108 - <h3 id="date-and-time">Date and Time</h3> 109 - <p>Anyone that has written Go previously, will probably already know 110 - what I am getting at here. For the uninitiated, parsing and formatting 111 - dates in Go requires a “layout”. This “layout” is based on magical 112 - reference date:</p> 113 - <pre><code>Mon Jan 2 15:04:05 MST 2006</code></pre> 114 - <p>Which is the date produced when you write the first seven natural 115 - numbers like so:</p> 116 - <pre><code>01/02 03:04:05 &#39;06 -0700</code></pre> 117 - <p>Parsing a string in <code>YYYY-MM-DD</code> format would look 118 - something like:</p> 119 - <div class="sourceCode" id="cb7"><pre class="sourceCode go"><code class="sourceCode go"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="kw">const</span> layout <span class="op">=</span> <span class="st">&quot;2006-01-02&quot;</span></span> 120 - <span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a>time<span class="op">.</span>Parse<span class="op">(</span>layout<span class="op">,</span> <span class="st">&quot;2020-08-01&quot;</span><span class="op">)</span></span></code></pre></div> 121 - <p>This so-called “intuitive” method of formatting dates doesn’t allow 122 - you to print <code>0000 hrs</code> as <code>2400 hrs</code>, it doesn’t 123 - allow you to omit the leading zero in 24 hour formats. It is rife with 124 - inconveniences, if only there were a <a 125 - href="https://man7.org/linux/man-pages/man3/strftime.3.html">tried and 126 - tested</a> date formatting convention …</p> 127 - <h3 id="statements-over-expressions">Statements over Expressions</h3> 128 - <p>Statements have side effects, expressions return values. More often 129 - than not, expressions are easier to understand at a glance: evaluate the 130 - LHS and assign the same to the RHS.</p> 131 - <p>Rust allows you to create local namespaces, and treats blocks 132 - (<code>{}</code>) as expressions:</p> 133 - <div class="sourceCode" id="cb8"><pre 134 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> twenty_seven <span class="op">=</span> <span class="op">{</span></span> 135 - <span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> three <span class="op">=</span> <span class="dv">1</span> <span class="op">+</span> <span class="dv">2</span><span class="op">;</span></span> 136 - <span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> nine <span class="op">=</span> three <span class="op">*</span> three<span class="op">;</span></span> 137 - <span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> nine <span class="op">*</span> three</span> 138 - <span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="op">};</span></span></code></pre></div> 139 - <p>The Go equivalent of the same:</p> 140 - <div class="sourceCode" id="cb9"><pre class="sourceCode go"><code class="sourceCode go"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a>twenty_seven <span class="op">:=</span> <span class="ot">nil</span></span> 141 - <span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a></span> 142 - <span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a>three <span class="op">:=</span> <span class="dv">1</span> <span class="op">+</span> <span class="dv">2</span></span> 143 - <span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a>nine <span class="op">:=</span> three <span class="op">*</span> three</span> 144 - <span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a>twenty_seven <span class="op">=</span> nine <span class="op">*</span> three</span></code></pre></div> 145 - <h3 id="erroring-out-on-unused-variables">Erroring out on unused 146 - variables</h3> 147 - <p>Want to quickly prototype something? Go says no! In all seriousness, 148 - a warning would suffice, I don’t want to have to go back and comment 149 - each unused import out, only to come back and uncomment them a few 150 - seconds later.</p> 151 - <h3 id="error-handling">Error handling</h3> 152 - <div class="sourceCode" id="cb10"><pre 153 - class="sourceCode go"><code class="sourceCode go"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="cf">if</span> err <span class="op">!=</span> <span class="ot">nil</span> <span class="op">{</span> <span class="op">...</span> <span class="op">}</span></span></code></pre></div> 154 - <p>Need I say more? I will, for good measure:</p> 155 - <ol type="1"> 156 - <li>Error handling is optional</li> 157 - <li>Errors are propagated via a clunky <code>if</code> + 158 - <code>return</code> statement</li> 159 - </ol> 160 - <p>I prefer Haskell’s “Monadic” error handling, which is employed by 161 - Rust as well:</p> 162 - <div class="sourceCode" id="cb11"><pre 163 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="co">// 1. error handling is compulsory</span></span> 164 - <span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a><span class="co">// 2. errors are propagated with the `?` operator</span></span> 165 - <span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> foo() <span class="op">-&gt;</span> <span class="dt">Result</span><span class="op">&lt;</span><span class="dt">String</span><span class="op">,</span> <span class="pp">io::</span><span class="bu">Error</span><span class="op">&gt;</span> <span class="op">{</span></span> 166 - <span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> <span class="kw">mut</span> f <span class="op">=</span> <span class="pp">File::</span>open(<span class="st">&quot;foo.txt&quot;</span>)<span class="op">?;</span> <span class="co">// return if error</span></span> 167 - <span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> <span class="kw">mut</span> s <span class="op">=</span> <span class="dt">String</span><span class="pp">::</span>new()<span class="op">;</span></span> 168 - <span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a></span> 169 - <span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a> f<span class="op">.</span>read_to_string(<span class="op">&amp;</span><span class="kw">mut</span> s)<span class="op">?;</span> <span class="co">// return if error</span></span> 170 - <span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a></span> 171 - <span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a> <span class="cn">Ok</span>(s) <span class="co">// all good, return a string inside a `Result` context</span></span> 172 - <span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span> 173 - <span id="cb11-11"><a href="#cb11-11" aria-hidden="true" tabindex="-1"></a></span> 174 - <span id="cb11-12"><a href="#cb11-12" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> main() <span class="op">{</span></span> 175 - <span id="cb11-13"><a href="#cb11-13" aria-hidden="true" tabindex="-1"></a> <span class="co">// `contents` is an enum known as Result:</span></span> 176 - <span id="cb11-14"><a href="#cb11-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> contents <span class="op">=</span> foo()<span class="op">;</span></span> 177 - <span id="cb11-15"><a href="#cb11-15" aria-hidden="true" tabindex="-1"></a> <span class="cf">match</span> contents <span class="op">{</span></span> 178 - <span id="cb11-16"><a href="#cb11-16" aria-hidden="true" tabindex="-1"></a> <span class="cn">Ok</span>(c) <span class="op">=&gt;</span> <span class="pp">println!</span>(c)<span class="op">,</span></span> 179 - <span id="cb11-17"><a href="#cb11-17" aria-hidden="true" tabindex="-1"></a> <span class="cn">Err</span>(e) <span class="op">=&gt;</span> <span class="pp">eprintln!</span>(e)</span> 180 - <span id="cb11-18"><a href="#cb11-18" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 181 - <span id="cb11-19"><a href="#cb11-19" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 182 - <h3 id="conclusion">Conclusion</h3> 183 - <p>I did not want to conclude without talking about stylistic choices, 184 - lack of metaprogramming, bizzare export rules, but, I am too busy 185 - converting my <code>interface{}</code> types into actual generic code 186 - for Go v2.</p> 187 - 188 - </div> 189 - 190 - <div class="intro"> 191 - Hi. 192 - <div class="hot-links"> 193 - <a href="/index.xml" class="feed-button">Subscribe</a> 194 - </div> 195 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 196 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 197 - <p>Reach out at oppili@libera.chat.</p> 198 - </div> 199 - 200 - <a href="/" class="post-end-link">Home</a> 201 - <span>/</span> 202 - <a href="/posts" class="post-end-link">Posts</a> 203 - <span>/</span> 204 - <a class="post-end-link">Gripes With Go</a> 205 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/gripes_with_go.md 206 - ">View Raw</a> 207 - </div> 208 - </div> 209 - </body> 210 - </html>
-86
docs/posts/hold_position!/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Hold Position!"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/hold_position!"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Hold Position! · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Hold Position!</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/hold_position!.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 30/07 — 2019 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 9.19 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 1.0 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Hold Position! 43 - </h1> 44 - <div class="post-text"> 45 - <p>Often times, when I run a vim command that makes “big” changes to a 46 - file (a macro or a <code>:vimgrep</code> command) I lose my original 47 - position and feel disoriented.</p> 48 - <p><em>Save position with <code>winsaveview()</code>!</em></p> 49 - <p>The <code>winsaveview()</code> command returns a 50 - <code>Dictionary</code> that contains information about the view of the 51 - current window. This includes the cursor line number, cursor coloumn, 52 - the top most line in the window and a couple of other values, none of 53 - which concern us.</p> 54 - <p>Before running our command (one that jumps around the buffer, a lot), 55 - we save our view, and restore it once its done, with 56 - <code>winrestview</code>.</p> 57 - <pre><code>let view = winsaveview() 58 - s/\s\+$//gc &quot; find and (confirm) replace trailing blanks 59 - winrestview(view) &quot; restore our original view!</code></pre> 60 - <p>It might seem a little overkill in the above example, just use `` 61 - (double backticks) instead, but it comes in handy when you run your file 62 - through heavier filtering.</p> 63 - 64 - </div> 65 - 66 - <div class="intro"> 67 - Hi. 68 - <div class="hot-links"> 69 - <a href="/index.xml" class="feed-button">Subscribe</a> 70 - </div> 71 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 72 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 73 - <p>Reach out at oppili@libera.chat.</p> 74 - </div> 75 - 76 - <a href="/" class="post-end-link">Home</a> 77 - <span>/</span> 78 - <a href="/posts" class="post-end-link">Posts</a> 79 - <span>/</span> 80 - <a class="post-end-link">Hold Position!</a> 81 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/hold_position!.md 82 - ">View Raw</a> 83 - </div> 84 - </div> 85 - </body> 86 - </html>
-600
docs/posts/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="alternate" type="application/atom+xml" title="oppili's micro musings" href="/index.xml"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="oppili's site"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="oppili's micro musings"> 13 - <meta property="og:url" content="https://oppi.li"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a class="post-end-link">Posts</a> 22 - 23 - <h1>Posts</h1> 24 - <div class="separator"></div> 25 - <table> 26 - 27 - <tr> 28 - <td class=table-post> 29 - <div class="date"> 30 - 31/07 — 2025 31 - </div> 32 - <a href="/posts/mounting_the_atmosphere" class="post-link"> 33 - <span class="post-link">Mounting The Atmosphere</span> 34 - </a> 35 - </td> 36 - <td class=table-stats> 37 - <span class="stats-number"> 38 - 4.3 39 - </span> 40 - <span class=stats-unit>min</span> 41 - </td> 42 - </tr> 43 - 44 - <tr> 45 - <td class=table-post> 46 - <div class="date"> 47 - 24/05 — 2025 48 - </div> 49 - <a href="/posts/configuring_jujutsu" class="post-link"> 50 - <span class="post-link">Configuring Jujutsu</span> 51 - </a> 52 - </td> 53 - <td class=table-stats> 54 - <span class="stats-number"> 55 - 9.5 56 - </span> 57 - <span class=stats-unit>min</span> 58 - </td> 59 - </tr> 60 - 61 - <tr> 62 - <td class=table-post> 63 - <div class="date"> 64 - 21/05 — 2025 65 - </div> 66 - <a href="/posts/tales_from_mainframe_modernization" class="post-link"> 67 - <span class="post-link">Tales From Mainframe Modernization</span> 68 - </a> 69 - </td> 70 - <td class=table-stats> 71 - <span class="stats-number"> 72 - 3.9 73 - </span> 74 - <span class=stats-unit>min</span> 75 - </td> 76 - </tr> 77 - 78 - <tr> 79 - <td class=table-post> 80 - <div class="date"> 81 - 27/11 — 2024 82 - </div> 83 - <a href="/posts/OSC-52" class="post-link"> 84 - <span class="post-link">OSC-52</span> 85 - </a> 86 - </td> 87 - <td class=table-stats> 88 - <span class="stats-number"> 89 - 1.9 90 - </span> 91 - <span class=stats-unit>min</span> 92 - </td> 93 - </tr> 94 - 95 - <tr> 96 - <td class=table-post> 97 - <div class="date"> 98 - 01/08 — 2024 99 - </div> 100 - <a href="/posts/introducing_tablespoon" class="post-link"> 101 - <span class="post-link">Introducing Tablespoon</span> 102 - </a> 103 - </td> 104 - <td class=table-stats> 105 - <span class="stats-number"> 106 - 4.5 107 - </span> 108 - <span class=stats-unit>min</span> 109 - </td> 110 - </tr> 111 - 112 - <tr> 113 - <td class=table-post> 114 - <div class="date"> 115 - 29/05 — 2024 116 - </div> 117 - <a href="/posts/snip_snap" class="post-link"> 118 - <span class="post-link">Snip Snap</span> 119 - </a> 120 - </td> 121 - <td class=table-stats> 122 - <span class="stats-number"> 123 - 2.1 124 - </span> 125 - <span class=stats-unit>min</span> 126 - </td> 127 - </tr> 128 - 129 - <tr> 130 - <td class=table-post> 131 - <div class="date"> 132 - 18/06 — 2023 133 - </div> 134 - <a href="/posts/plain_text_journaling" class="post-link"> 135 - <span class="post-link">Plain Text Journaling</span> 136 - </a> 137 - </td> 138 - <td class=table-stats> 139 - <span class="stats-number"> 140 - 8.9 141 - </span> 142 - <span class=stats-unit>min</span> 143 - </td> 144 - </tr> 145 - 146 - <tr> 147 - <td class=table-post> 148 - <div class="date"> 149 - 03/09 — 2022 150 - </div> 151 - <a href="/posts/curing_a_case_of_git-UX" class="post-link"> 152 - <span class="post-link">Curing A Case Of Git-UX</span> 153 - </a> 154 - </td> 155 - <td class=table-stats> 156 - <span class="stats-number"> 157 - 9.6 158 - </span> 159 - <span class=stats-unit>min</span> 160 - </td> 161 - </tr> 162 - 163 - <tr> 164 - <td class=table-post> 165 - <div class="date"> 166 - 28/08 — 2022 167 - </div> 168 - <a href="/posts/programming_on_34_keys" class="post-link"> 169 - <span class="post-link">Programming On 34 Keys</span> 170 - </a> 171 - </td> 172 - <td class=table-stats> 173 - <span class="stats-number"> 174 - 6.2 175 - </span> 176 - <span class=stats-unit>min</span> 177 - </td> 178 - </tr> 179 - 180 - <tr> 181 - <td class=table-post> 182 - <div class="date"> 183 - 02/08 — 2022 184 - </div> 185 - <a href="/posts/a_reference_counted_afterlife" class="post-link"> 186 - <span class="post-link">A Reference Counted Afterlife</span> 187 - </a> 188 - </td> 189 - <td class=table-stats> 190 - <span class="stats-number"> 191 - 1.6 192 - </span> 193 - <span class=stats-unit>min</span> 194 - </td> 195 - </tr> 196 - 197 - <tr> 198 - <td class=table-post> 199 - <div class="date"> 200 - 13/06 — 2022 201 - </div> 202 - <a href="/posts/lotus58" class="post-link"> 203 - <span class="post-link">Lotus58</span> 204 - </a> 205 - </td> 206 - <td class=table-stats> 207 - <span class="stats-number"> 208 - 4.8 209 - </span> 210 - <span class=stats-unit>min</span> 211 - </td> 212 - </tr> 213 - 214 - <tr> 215 - <td class=table-post> 216 - <div class="date"> 217 - 26/01 — 2022 218 - </div> 219 - <a href="/posts/lightweight_linting" class="post-link"> 220 - <span class="post-link">Lightweight Linting</span> 221 - </a> 222 - </td> 223 - <td class=table-stats> 224 - <span class="stats-number"> 225 - 8.6 226 - </span> 227 - <span class=stats-unit>min</span> 228 - </td> 229 - </tr> 230 - 231 - <tr> 232 - <td class=table-post> 233 - <div class="date"> 234 - 05/10 — 2021 235 - </div> 236 - <a href="/posts/novice_nix:_flake_templates" class="post-link"> 237 - <span class="post-link">Novice Nix: Flake Templates</span> 238 - </a> 239 - </td> 240 - <td class=table-stats> 241 - <span class="stats-number"> 242 - 5.5 243 - </span> 244 - <span class=stats-unit>min</span> 245 - </td> 246 - </tr> 247 - 248 - <tr> 249 - <td class=table-post> 250 - <div class="date"> 251 - 11/04 — 2021 252 - </div> 253 - <a href="/posts/SDL2_devlog" class="post-link"> 254 - <span class="post-link">SDL2 Devlog</span> 255 - </a> 256 - </td> 257 - <td class=table-stats> 258 - <span class="stats-number"> 259 - 10.0 260 - </span> 261 - <span class=stats-unit>min</span> 262 - </td> 263 - </tr> 264 - 265 - <tr> 266 - <td class=table-post> 267 - <div class="date"> 268 - 17/10 — 2020 269 - </div> 270 - <a href="/posts/self-hosting_git" class="post-link"> 271 - <span class="post-link">Self-hosting Git</span> 272 - </a> 273 - </td> 274 - <td class=table-stats> 275 - <span class="stats-number"> 276 - 5.4 277 - </span> 278 - <span class=stats-unit>min</span> 279 - </td> 280 - </tr> 281 - 282 - <tr> 283 - <td class=table-post> 284 - <div class="date"> 285 - 01/09 — 2020 286 - </div> 287 - <a href="/posts/nixOS" class="post-link"> 288 - <span class="post-link">NixOS</span> 289 - </a> 290 - </td> 291 - <td class=table-stats> 292 - <span class="stats-number"> 293 - 3.4 294 - </span> 295 - <span class=stats-unit>min</span> 296 - </td> 297 - </tr> 298 - 299 - <tr> 300 - <td class=table-post> 301 - <div class="date"> 302 - 01/08 — 2020 303 - </div> 304 - <a href="/posts/gripes_with_go" class="post-link"> 305 - <span class="post-link">Gripes With Go</span> 306 - </a> 307 - </td> 308 - <td class=table-stats> 309 - <span class="stats-number"> 310 - 4.9 311 - </span> 312 - <span class=stats-unit>min</span> 313 - </td> 314 - </tr> 315 - 316 - <tr> 317 - <td class=table-post> 318 - <div class="date"> 319 - 17/06 — 2020 320 - </div> 321 - <a href="/posts/turing_complete_type_systems" class="post-link"> 322 - <span class="post-link">Turing Complete Type Systems</span> 323 - </a> 324 - </td> 325 - <td class=table-stats> 326 - <span class="stats-number"> 327 - 0.9 328 - </span> 329 - <span class=stats-unit>min</span> 330 - </td> 331 - </tr> 332 - 333 - <tr> 334 - <td class=table-post> 335 - <div class="date"> 336 - 08/05 — 2020 337 - </div> 338 - <a href="/posts/auto-currying_rust_functions" class="post-link"> 339 - <span class="post-link">Auto-currying Rust Functions</span> 340 - </a> 341 - </td> 342 - <td class=table-stats> 343 - <span class="stats-number"> 344 - 25.1 345 - </span> 346 - <span class=stats-unit>min</span> 347 - </td> 348 - </tr> 349 - 350 - <tr> 351 - <td class=table-post> 352 - <div class="date"> 353 - 08/04 — 2020 354 - </div> 355 - <a href="/posts/pixel_art_in_GIMP" class="post-link"> 356 - <span class="post-link">Pixel Art In GIMP</span> 357 - </a> 358 - </td> 359 - <td class=table-stats> 360 - <span class="stats-number"> 361 - 5.0 362 - </span> 363 - <span class=stats-unit>min</span> 364 - </td> 365 - </tr> 366 - 367 - <tr> 368 - <td class=table-post> 369 - <div class="date"> 370 - 31/03 — 2020 371 - </div> 372 - <a href="/posts/rapid_refactoring_with_vim" class="post-link"> 373 - <span class="post-link">Rapid Refactoring With Vim</span> 374 - </a> 375 - </td> 376 - <td class=table-stats> 377 - <span class="stats-number"> 378 - 5.4 379 - </span> 380 - <span class=stats-unit>min</span> 381 - </td> 382 - </tr> 383 - 384 - <tr> 385 - <td class=table-post> 386 - <div class="date"> 387 - 16/03 — 2020 388 - </div> 389 - <a href="/posts/font_size_fallacies" class="post-link"> 390 - <span class="post-link">Font Size Fallacies</span> 391 - </a> 392 - </td> 393 - <td class=table-stats> 394 - <span class="stats-number"> 395 - 3.3 396 - </span> 397 - <span class=stats-unit>min</span> 398 - </td> 399 - </tr> 400 - 401 - <tr> 402 - <td class=table-post> 403 - <div class="date"> 404 - 07/03 — 2020 405 - </div> 406 - <a href="/posts/termux_tandem" class="post-link"> 407 - <span class="post-link">Termux Tandem</span> 408 - </a> 409 - </td> 410 - <td class=table-stats> 411 - <span class="stats-number"> 412 - 1.6 413 - </span> 414 - <span class=stats-unit>min</span> 415 - </td> 416 - </tr> 417 - 418 - <tr> 419 - <td class=table-post> 420 - <div class="date"> 421 - 07/02 — 2020 422 - </div> 423 - <a href="/posts/call_to_ARMs" class="post-link"> 424 - <span class="post-link">Call To ARMs</span> 425 - </a> 426 - </td> 427 - <td class=table-stats> 428 - <span class="stats-number"> 429 - 2.3 430 - </span> 431 - <span class=stats-unit>min</span> 432 - </td> 433 - </tr> 434 - 435 - <tr> 436 - <td class=table-post> 437 - <div class="date"> 438 - 30/12 — 2019 439 - </div> 440 - <a href="/posts/color_conundrum" class="post-link"> 441 - <span class="post-link">Color Conundrum</span> 442 - </a> 443 - </td> 444 - <td class=table-stats> 445 - <span class="stats-number"> 446 - 1.4 447 - </span> 448 - <span class=stats-unit>min</span> 449 - </td> 450 - </tr> 451 - 452 - <tr> 453 - <td class=table-post> 454 - <div class="date"> 455 - 22/11 — 2019 456 - </div> 457 - <a href="/posts/static_sites_with_bash" class="post-link"> 458 - <span class="post-link">Static Sites With Bash</span> 459 - </a> 460 - </td> 461 - <td class=table-stats> 462 - <span class="stats-number"> 463 - 1.5 464 - </span> 465 - <span class=stats-unit>min</span> 466 - </td> 467 - </tr> 468 - 469 - <tr> 470 - <td class=table-post> 471 - <div class="date"> 472 - 06/11 — 2019 473 - </div> 474 - <a href="/posts/my_setup" class="post-link"> 475 - <span class="post-link">My Setup</span> 476 - </a> 477 - </td> 478 - <td class=table-stats> 479 - <span class="stats-number"> 480 - 1.0 481 - </span> 482 - <span class=stats-unit>min</span> 483 - </td> 484 - </tr> 485 - 486 - <tr> 487 - <td class=table-post> 488 - <div class="date"> 489 - 12/10 — 2019 490 - </div> 491 - <a href="/posts/WPA_woes" class="post-link"> 492 - <span class="post-link">WPA Woes</span> 493 - </a> 494 - </td> 495 - <td class=table-stats> 496 - <span class="stats-number"> 497 - 1.1 498 - </span> 499 - <span class=stats-unit>min</span> 500 - </td> 501 - </tr> 502 - 503 - <tr> 504 - <td class=table-post> 505 - <div class="date"> 506 - 07/08 — 2019 507 - </div> 508 - <a href="/posts/bye_bye_BDFs" class="post-link"> 509 - <span class="post-link">Bye Bye BDFs</span> 510 - </a> 511 - </td> 512 - <td class=table-stats> 513 - <span class="stats-number"> 514 - 1.0 515 - </span> 516 - <span class=stats-unit>min</span> 517 - </td> 518 - </tr> 519 - 520 - <tr> 521 - <td class=table-post> 522 - <div class="date"> 523 - 02/08 — 2019 524 - </div> 525 - <a href="/posts/onivim_sucks" class="post-link"> 526 - <span class="post-link">Onivim Sucks</span> 527 - </a> 528 - </td> 529 - <td class=table-stats> 530 - <span class="stats-number"> 531 - 1.3 532 - </span> 533 - <span class=stats-unit>min</span> 534 - </td> 535 - </tr> 536 - 537 - <tr> 538 - <td class=table-post> 539 - <div class="date"> 540 - 30/07 — 2019 541 - </div> 542 - <a href="/posts/bash_harder_with_vim" class="post-link"> 543 - <span class="post-link">Bash Harder With Vim</span> 544 - </a> 545 - </td> 546 - <td class=table-stats> 547 - <span class="stats-number"> 548 - 1.6 549 - </span> 550 - <span class=stats-unit>min</span> 551 - </td> 552 - </tr> 553 - 554 - <tr> 555 - <td class=table-post> 556 - <div class="date"> 557 - 30/07 — 2019 558 - </div> 559 - <a href="/posts/hold_position!" class="post-link"> 560 - <span class="post-link">Hold Position!</span> 561 - </a> 562 - </td> 563 - <td class=table-stats> 564 - <span class="stats-number"> 565 - 1.0 566 - </span> 567 - <span class=stats-unit>min</span> 568 - </td> 569 - </tr> 570 - 571 - <tr> 572 - <td class=table-post> 573 - <div class="date"> 574 - 30/07 — 2019 575 - </div> 576 - <a href="/posts/get_better_at_yanking_and_putting_in_vim" class="post-link"> 577 - <span class="post-link">Get Better At Yanking And Putting In Vim</span> 578 - </a> 579 - </td> 580 - <td class=table-stats> 581 - <span class="stats-number"> 582 - 0.9 583 - </span> 584 - <span class=stats-unit>min</span> 585 - </td> 586 - </tr> 587 - </table> 588 - <div class="separator"></div> 589 - <div class="footer"> 590 - <a href="https://tangled.sh/@oppi.li">Code</a> · 591 - <a href="https://bsky.app/profile/oppi.li">Bluesky</a> · 592 - <a href="mailto:me@oppi.li">Mail</a> 593 - <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/"> 594 - <img class="footimgs" src="https://d33wubrfki0l68.cloudfront.net/94387e9d77fbc8b4360db81e72603ecba3df94a7/632bc/static/cc.svg"> 595 - </a> 596 - </div> 597 - </div> 598 - </div> 599 - </body> 600 - </html>
-207
docs/posts/introducing_tablespoon/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Introducing Tablespoon"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/introducing_tablespoon"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Introducing Tablespoon · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Introducing Tablespoon</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/introducing_tablespoon.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 01/08 — 2024 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 72.33 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 4.5 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Introducing Tablespoon 43 - </h1> 44 - <div class="post-text"> 45 - <p><a href="https://git.peppe.rs/languages/tbsp">tbsp</a> (tree-based 46 - source-processing language) is an awk-like language that operates on 47 - tree-sitter syntax trees. To motivate the need for such a program, we 48 - could begin by writing a markdown-to-html converter using 49 - <code>tbsp</code> and <a 50 - href="https://github.com/tree-sitter-grammars/tree-sitter-markdown">tree-sitter-md</a>. 51 - We need some markdown to begin with:</p> 52 - <pre><code># 1 heading 53 - 54 - content of first paragraph 55 - 56 - ## 1.1 heading 57 - 58 - content of nested paragraph</code></pre> 59 - <p>For future reference, this markdown is parsed like so by 60 - tree-sitter-md (visualization generated by <a 61 - href="https://git.peppe.rs/cli/tree-viz">tree-viz</a>):</p> 62 - <pre><code>document 63 - | section 64 - | | atx_heading 65 - | | | atx_h1_marker &quot;#&quot; 66 - | | | heading_content inline &quot;1 heading&quot; 67 - | | paragraph 68 - | | | inline &quot;content of first paragraph&quot; 69 - | | section 70 - | | | atx_heading 71 - | | | | atx_h2_marker &quot;##&quot; 72 - | | | | heading_content inline &quot;1.1 heading&quot; 73 - | | | paragraph 74 - | | | | inline &quot;content of nested paragraph&quot;</code></pre> 75 - <p>Onto the converter itself. Every <code>tbsp</code> program is written 76 - as a collection of stanzas. Typically, we start with a stanza like 77 - so:</p> 78 - <pre><code>BEGIN { 79 - int depth = 0; 80 - 81 - print(&quot;&lt;html&gt;\n&quot;); 82 - print(&quot;&lt;body&gt;\n&quot;); 83 - }</code></pre> 84 - <p>The stanza begins with a “pattern”, in this case, <code>BEGIN</code>, 85 - and is followed a block of code. This block specifically, is executed 86 - right at the beginning, before traversing the parse tree. In this 87 - stanza, we set a “depth” variable to keep track of nesting of markdown 88 - headers, and begin our html document by printing the 89 - <code>&lt;html&gt;</code> and <code>&lt;body&gt;</code> tags.</p> 90 - <p>We can follow this stanza with an <code>END</code> stanza, that is 91 - executed after the traversal:</p> 92 - <pre><code>END { 93 - print(&quot;&lt;/body&gt;\n&quot;); 94 - print(&quot;&lt;/html&gt;\n&quot;); 95 - }</code></pre> 96 - <p>In this stanza, we close off the tags we opened at the start of the 97 - document. We can move onto the interesting bits of the conversion 98 - now:</p> 99 - <pre><code>enter section { 100 - depth += 1; 101 - } 102 - leave section { 103 - depth -= 1; 104 - }</code></pre> 105 - <p>The above stanzas begin with <code>enter</code> and 106 - <code>leave</code> clauses, followed by the name of a tree-sitter node 107 - kind: <code>section</code>. The <code>section</code> identifier is 108 - visible in the tree-visualization above, it encompasses a 109 - markdown-section, and is created for every markdown header. To 110 - understand how <code>tbsp</code> executes above stanzas:</p> 111 - <pre><code>document ... depth = 0 112 - | section &lt;-------- enter section (1) ... depth = 1 113 - | | atx_heading 114 - | | | inline 115 - | | paragraph 116 - | | | inline 117 - | | section &lt;----- enter section (2) ... depth = 2 118 - | | | atx_heading 119 - | | | | inline 120 - | | | paragraph 121 - | | | | inline 122 - | | | &lt;----------- leave section (2) ... depth = 1 123 - | | &lt;-------------- leave section (1) ... depth = 0 </code></pre> 124 - <p>The following stanzas should be self-explanatory now:</p> 125 - <pre><code>enter atx_heading { 126 - print(&quot;&lt;h&quot;); 127 - print(depth); 128 - print(&quot;&gt;&quot;); 129 - } 130 - leave atx_heading { 131 - print(&quot;&lt;/h&quot;); 132 - print(depth); 133 - print(&quot;&gt;\n&quot;); 134 - } 135 - 136 - enter inline { 137 - print(text(node)); 138 - }</code></pre> 139 - <p>But an explanation is included nonetheless:</p> 140 - <pre><code>document ... depth = 0 141 - | section &lt;-------- enter section (1) ... depth = 1 142 - | | atx_heading &lt;- enter atx_heading ... print &quot;&lt;h1&gt;&quot; 143 - | | | inline &lt;--- enter inline ... print .. 144 - | | | &lt;----------- leave atx_heading ... print &quot;&lt;/h1&gt;&quot; 145 - | | paragraph 146 - | | | inline &lt;--- enter inline ... print .. 147 - | | section &lt;----- enter section (2) ... depth = 2 148 - | | | atx_heading enter atx_heading ... print &quot;&lt;h2&gt;&quot; 149 - | | | | inline &lt;- enter inline ... print .. 150 - | | | | &lt;-------- leave atx_heading ... print &quot;&lt;/h2&gt;&quot; 151 - | | | paragraph 152 - | | | | inline &lt;- enter inline ... print .. 153 - | | | &lt;----------- leave section (2) ... depth = 1 154 - | | &lt;-------------- leave section (1) ... depth = 0 </code></pre> 155 - <p>The <a 156 - href="https://git.peppe.rs/languages/tbsp/tree/examples">examples</a> 157 - directory contains a complete markdown-to-html converter, along with a 158 - few other motivating examples.</p> 159 - <h3 id="usage">Usage</h3> 160 - <p>The <code>tbsp</code> evaluator is written in rust, use cargo to 161 - build and run:</p> 162 - <pre><code>cargo build --release 163 - ./target/release/tbsp --help</code></pre> 164 - <p><code>tbsp</code> requires three inputs:</p> 165 - <ul> 166 - <li>a <code>tbsp</code> program, referred to as “program file”</li> 167 - <li>a language</li> 168 - <li>an input file or some input text at stdin</li> 169 - </ul> 170 - <p>You can run the interpreter like so (this program prints an overview 171 - of a rust file):</p> 172 - <pre><code>$ ./target/release/tbsp \ 173 - -f./examples/code-overview/overview.tbsp \ 174 - -l rust \ 175 - src/main.rs 176 - module 177 - └╴struct Cli 178 - └╴trait Cli 179 - └╴fn program 180 - └╴fn language 181 - └╴fn file 182 - └╴fn try_consume_stdin 183 - └╴fn main</code></pre> 184 - 185 - </div> 186 - 187 - <div class="intro"> 188 - Hi. 189 - <div class="hot-links"> 190 - <a href="/index.xml" class="feed-button">Subscribe</a> 191 - </div> 192 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 193 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 194 - <p>Reach out at oppili@libera.chat.</p> 195 - </div> 196 - 197 - <a href="/" class="post-end-link">Home</a> 198 - <span>/</span> 199 - <a href="/posts" class="post-end-link">Posts</a> 200 - <span>/</span> 201 - <a class="post-end-link">Introducing Tablespoon</a> 202 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/introducing_tablespoon.md 203 - ">View Raw</a> 204 - </div> 205 - </div> 206 - </body> 207 - </html>
-381
docs/posts/lightweight_linting/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Lightweight Linting"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/lightweight_linting"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Lightweight Linting · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Lightweight Linting</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/lightweight_linting.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 26/01 — 2022 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 170.63 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 8.6 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Lightweight Linting 43 - </h1> 44 - <div class="post-text"> 45 - <p><a 46 - href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries">Tree-sitter</a> 47 - queries allow you to search for patterns in syntax trees, much like a 48 - regex would, in text. Combine that with some Rust glue to write simple, 49 - custom linters.</p> 50 - <h3 id="tree-sitter-syntax-trees">Tree-sitter syntax trees</h3> 51 - <p>Here is a quick crash course on syntax trees generated by 52 - tree-sitter. Syntax trees produced by tree-sitter are represented by 53 - S-expressions. The generated S-expression for the following Rust 54 - code,</p> 55 - <div class="sourceCode" id="cb1"><pre 56 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> main() <span class="op">{</span></span> 57 - <span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> x <span class="op">=</span> <span class="dv">2</span><span class="op">;</span></span> 58 - <span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 59 - <p>would be:</p> 60 - <div class="sourceCode" id="cb2"><pre 61 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>(source_file</span> 62 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> (function_item</span> 63 - <span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a> name: (identifier)</span> 64 - <span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> parameters: (parameters)</span> 65 - <span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> body: </span> 66 - <span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a> (block</span> 67 - <span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> (let_declaration </span> 68 - <span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> pattern: (identifier)</span> 69 - <span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> value: (integer_literal)))))</span></code></pre></div> 70 - <p>Syntax trees generated by tree-sitter have a couple of other cool 71 - properties: they are <em>lossless</em> syntax trees. Given a lossless 72 - syntax tree, you can regenerate the original source code in its 73 - entirety. Consider the following addition to our example:</p> 74 - <div class="sourceCode" id="cb3"><pre 75 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a> <span class="kw">fn</span> main() <span class="op">{</span></span> 76 - <span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="co">// a comment goes here</span></span> 77 - <span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> x <span class="op">=</span> <span class="dv">2</span><span class="op">;</span></span> 78 - <span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span></code></pre></div> 79 - <p>The tree-sitter syntax tree preserves the comment, while the typical 80 - abstract syntax tree wouldn’t:</p> 81 - <div class="sourceCode" id="cb4"><pre 82 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a> (source_file</span> 83 - <span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a> (function_item</span> 84 - <span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a> name: (identifier)</span> 85 - <span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a> parameters: (parameters)</span> 86 - <span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a> body:</span> 87 - <span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a> (block</span> 88 - <span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> (line_comment)</span> 89 - <span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a> (let_declaration</span> 90 - <span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a> pattern: (identifier)</span> 91 - <span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a> value: (integer_literal)))))</span></code></pre></div> 92 - <h3 id="tree-sitter-queries">Tree-sitter queries</h3> 93 - <p>Tree-sitter provides a DSL to match over CSTs. These queries resemble 94 - our S-expression syntax trees, here is a query to match all line 95 - comments in a Rust CST:</p> 96 - <div class="sourceCode" id="cb5"><pre 97 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a>(line_comment)</span> 98 - <span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a></span> 99 - <span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co">; matches the following rust code</span></span> 100 - <span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a><span class="co">; // a comment goes here</span></span></code></pre></div> 101 - <p>Neat, eh? But don’t take my word for it, give it a go on the <a 102 - href="https://tree-sitter.github.io/tree-sitter/playground">tree-sitter 103 - playground</a>. Type in a query like so:</p> 104 - <div class="sourceCode" id="cb6"><pre 105 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="co">; the web playground requires you to specify a &quot;capture&quot;</span></span> 106 - <span id="cb6-2"><a href="#cb6-2" aria-hidden="true" tabindex="-1"></a><span class="co">; you will notice the capture and the nodes it captured</span></span> 107 - <span id="cb6-3"><a href="#cb6-3" aria-hidden="true" tabindex="-1"></a><span class="co">; turn blue</span></span> 108 - <span id="cb6-4"><a href="#cb6-4" aria-hidden="true" tabindex="-1"></a>(line_comment) @capture</span></code></pre></div> 109 - <p>Here’s another to match <code>let</code> expressions that bind an 110 - integer to an identifier:</p> 111 - <div class="sourceCode" id="cb7"><pre 112 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a>(let_declaration</span> 113 - <span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a> pattern: (identifier)</span> 114 - <span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a> value: (integer_literal))</span> 115 - <span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> </span> 116 - <span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a><span class="co">; matches:</span></span> 117 - <span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a><span class="co">; let foo = 2;</span></span></code></pre></div> 118 - <p>We can <em>capture</em> nodes into variables:</p> 119 - <div class="sourceCode" id="cb8"><pre 120 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a>(let_declaration </span> 121 - <span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a> pattern: (identifier) @my-capture</span> 122 - <span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a> value: (integer_literal))</span> 123 - <span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> </span> 124 - <span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a><span class="co">; matches:</span></span> 125 - <span id="cb8-6"><a href="#cb8-6" aria-hidden="true" tabindex="-1"></a><span class="co">; let foo = 2;</span></span> 126 - <span id="cb8-7"><a href="#cb8-7" aria-hidden="true" tabindex="-1"></a></span> 127 - <span id="cb8-8"><a href="#cb8-8" aria-hidden="true" tabindex="-1"></a><span class="co">; captures:</span></span> 128 - <span id="cb8-9"><a href="#cb8-9" aria-hidden="true" tabindex="-1"></a><span class="co">; foo</span></span></code></pre></div> 129 - <p>And apply certain <em>predicates</em> to captures:</p> 130 - <div class="sourceCode" id="cb9"><pre 131 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a>((let_declaration</span> 132 - <span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a> pattern: (identifier) @my-capture</span> 133 - <span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a> value: (integer_literal))</span> 134 - <span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a> (<span class="sc">#e</span>q? @my-capture <span class="st">&quot;foo&quot;</span>))</span> 135 - <span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a> </span> 136 - <span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a><span class="co">; matches:</span></span> 137 - <span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="co">; let foo = 2;</span></span> 138 - <span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a></span> 139 - <span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="co">; and not:</span></span> 140 - <span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="co">; let bar = 2;</span></span></code></pre></div> 141 - <p>The <code>#match?</code> predicate checks if a capture matches a 142 - regex:</p> 143 - <div class="sourceCode" id="cb10"><pre 144 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a>((let_declaration</span> 145 - <span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a> pattern: (identifier) @my-capture</span> 146 - <span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a> value: (integer_literal))</span> 147 - <span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a> (#match? @my-capture <span class="st">&quot;foo|bar&quot;</span>))</span> 148 - <span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a> </span> 149 - <span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a><span class="co">; matches both `foo` and `bar`:</span></span> 150 - <span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a><span class="co">; let foo = 2;</span></span> 151 - <span id="cb10-8"><a href="#cb10-8" aria-hidden="true" tabindex="-1"></a><span class="co">; let bar = 2;</span></span></code></pre></div> 152 - <p>Exhibit indifference, as a stoic programmer would, with the 153 - <em>wildcard</em> pattern:</p> 154 - <div class="sourceCode" id="cb11"><pre 155 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a>(let_declaration</span> 156 - <span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a> pattern: (identifier)</span> 157 - <span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a> value: (<span class="op">_</span>))</span> 158 - <span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a> </span> 159 - <span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a><span class="co">; matches:</span></span> 160 - <span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a><span class="co">; let foo = &quot;foo&quot;;</span></span> 161 - <span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a><span class="co">; let foo = 42;</span></span> 162 - <span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a><span class="co">; let foo = bar;</span></span></code></pre></div> 163 - <p><a 164 - href="https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries">The 165 - documentation</a> does the tree-sitter query DSL more justice, but we 166 - now know enough to write our first lint.</p> 167 - <h3 id="write-you-a-tree-sitter-lint">Write you a tree-sitter lint</h3> 168 - <p>Strings in <code>std::env</code> functions are error prone:</p> 169 - <div class="sourceCode" id="cb12"><pre 170 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a><span class="pp">std::env::</span>remove_var(<span class="st">&quot;RUST_BACKTACE&quot;</span>)<span class="op">;</span></span> 171 - <span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a> <span class="co">// ^^^^ &quot;TACE&quot; instead of &quot;TRACE&quot;</span></span></code></pre></div> 172 - <p>I prefer this instead:</p> 173 - <div class="sourceCode" id="cb13"><pre 174 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb13-1"><a href="#cb13-1" aria-hidden="true" tabindex="-1"></a><span class="co">// somewhere in a module that is well spellchecked</span></span> 175 - <span id="cb13-2"><a href="#cb13-2" aria-hidden="true" tabindex="-1"></a><span class="kw">static</span> BACKTRACE<span class="op">:</span> <span class="op">&amp;</span><span class="dt">str</span> <span class="op">=</span> <span class="st">&quot;RUST_BACKTRACE&quot;</span><span class="op">;</span></span> 176 - <span id="cb13-3"><a href="#cb13-3" aria-hidden="true" tabindex="-1"></a></span> 177 - <span id="cb13-4"><a href="#cb13-4" aria-hidden="true" tabindex="-1"></a><span class="co">// rest of the codebase</span></span> 178 - <span id="cb13-5"><a href="#cb13-5" aria-hidden="true" tabindex="-1"></a><span class="pp">std::env::</span>remove_var(BACKTRACE)<span class="op">;</span></span></code></pre></div> 179 - <p>Let’s write a lint to find <code>std::env</code> functions that use 180 - strings. Put aside the effectiveness of this lint for the moment, and 181 - take a stab at writing a tree-sitter query. For reference, a function 182 - call like so:</p> 183 - <div class="sourceCode" id="cb14"><pre 184 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb14-1"><a href="#cb14-1" aria-hidden="true" tabindex="-1"></a>remove_var(<span class="st">&quot;RUST_BACKTRACE&quot;</span>)</span></code></pre></div> 185 - <p>Produces the following S-expression:</p> 186 - <div class="sourceCode" id="cb15"><pre 187 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb15-1"><a href="#cb15-1" aria-hidden="true" tabindex="-1"></a>(call_expression</span> 188 - <span id="cb15-2"><a href="#cb15-2" aria-hidden="true" tabindex="-1"></a> function: (identifier)</span> 189 - <span id="cb15-3"><a href="#cb15-3" aria-hidden="true" tabindex="-1"></a> arguments: (arguments (string_literal)))</span></code></pre></div> 190 - <p>We are definitely looking for a <code>call_expression</code>:</p> 191 - <div class="sourceCode" id="cb16"><pre 192 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb16-1"><a href="#cb16-1" aria-hidden="true" tabindex="-1"></a>(call_expression) @raise</span></code></pre></div> 193 - <p>Whose function name matches <code>std::env::var</code> or 194 - <code>std::env::remove_var</code> at the very least (I know, I know, 195 - this isn’t the most optimal regex):</p> 196 - <div class="sourceCode" id="cb17"><pre 197 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a>((call_expression</span> 198 - <span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a> function: (<span class="op">_</span>) @fn-name) @raise</span> 199 - <span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a> (#match? @fn-name <span class="st">&quot;std::env::(var|remove_var)&quot;</span>))</span></code></pre></div> 200 - <p>Let’s turn that <code>std::</code> prefix optional:</p> 201 - <div class="sourceCode" id="cb18"><pre 202 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb18-1"><a href="#cb18-1" aria-hidden="true" tabindex="-1"></a>((call_expression</span> 203 - <span id="cb18-2"><a href="#cb18-2" aria-hidden="true" tabindex="-1"></a> function: (<span class="op">_</span>) @fn-name) @raise</span> 204 - <span id="cb18-3"><a href="#cb18-3" aria-hidden="true" tabindex="-1"></a> (#match? @fn-name <span class="st">&quot;(std::|)env::(var|remove_var)&quot;</span>))</span></code></pre></div> 205 - <p>And ensure that <code>arguments</code> is a string:</p> 206 - <div class="sourceCode" id="cb19"><pre 207 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb19-1"><a href="#cb19-1" aria-hidden="true" tabindex="-1"></a>((call_expression</span> 208 - <span id="cb19-2"><a href="#cb19-2" aria-hidden="true" tabindex="-1"></a> function: (<span class="op">_</span>) @fn-name</span> 209 - <span id="cb19-3"><a href="#cb19-3" aria-hidden="true" tabindex="-1"></a> arguments: (arguments (string_literal)))</span> 210 - <span id="cb19-4"><a href="#cb19-4" aria-hidden="true" tabindex="-1"></a> (#match? @fn-name <span class="st">&quot;(std::|)env::(var|remove_var)&quot;</span>))</span></code></pre></div> 211 - <h3 id="running-our-linter">Running our linter</h3> 212 - <p>We could always plug our query into the web playground, but let’s go 213 - a step further:</p> 214 - <div class="sourceCode" id="cb20"><pre 215 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb20-1"><a href="#cb20-1" aria-hidden="true" tabindex="-1"></a><span class="ex">cargo</span> new <span class="at">--bin</span> toy-lint</span></code></pre></div> 216 - <p>Add <code>tree-sitter</code> and <code>tree-sitter-rust</code> to 217 - your dependencies:</p> 218 - <div class="sourceCode" id="cb21"><pre 219 - class="sourceCode toml"><code class="sourceCode toml"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true" tabindex="-1"></a><span class="co"># within Cargo.toml</span></span> 220 - <span id="cb21-2"><a href="#cb21-2" aria-hidden="true" tabindex="-1"></a><span class="kw">[dependencies]</span></span> 221 - <span id="cb21-3"><a href="#cb21-3" aria-hidden="true" tabindex="-1"></a><span class="dt">tree-sitter</span> <span class="op">=</span> <span class="st">&quot;0.20&quot;</span></span> 222 - <span id="cb21-4"><a href="#cb21-4" aria-hidden="true" tabindex="-1"></a></span> 223 - <span id="cb21-5"><a href="#cb21-5" aria-hidden="true" tabindex="-1"></a><span class="kw">[dependencies.tree-sitter-rust]</span></span> 224 - <span id="cb21-6"><a href="#cb21-6" aria-hidden="true" tabindex="-1"></a><span class="dt">git</span> <span class="op">=</span> <span class="st">&quot;https://github.com/tree-sitter/tree-sitter-rust&quot;</span></span></code></pre></div> 225 - <p>Let’s load in some Rust code to work with. As <a 226 - href="https://en.wikipedia.org/wiki/Self-reference">an ode to Gödel</a> 227 - (G<code>ode</code>l?), why not load in our linter itself:</p> 228 - <div class="sourceCode" id="cb22"><pre 229 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true" tabindex="-1"></a><span class="kw">fn</span> main() <span class="op">{</span></span> 230 - <span id="cb22-2"><a href="#cb22-2" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> src <span class="op">=</span> <span class="pp">include_str!</span>(<span class="st">&quot;main.rs&quot;</span>)<span class="op">;</span></span> 231 - <span id="cb22-3"><a href="#cb22-3" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 232 - <p>Most tree-sitter APIs require a reference to a <code>Language</code> 233 - struct, we will be working with Rust if you haven’t already guessed:</p> 234 - <div class="sourceCode" id="cb23"><pre 235 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb23-1"><a href="#cb23-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">tree_sitter::</span>Language<span class="op">;</span></span> 236 - <span id="cb23-2"><a href="#cb23-2" aria-hidden="true" tabindex="-1"></a></span> 237 - <span id="cb23-3"><a href="#cb23-3" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> rust_lang<span class="op">:</span> Language <span class="op">=</span> <span class="pp">tree_sitter_rust::</span>language()<span class="op">;</span></span></code></pre></div> 238 - <p>Enough scaffolding, let’s parse some Rust:</p> 239 - <div class="sourceCode" id="cb24"><pre 240 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb24-1"><a href="#cb24-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">tree_sitter::</span>Parser<span class="op">;</span></span> 241 - <span id="cb24-2"><a href="#cb24-2" aria-hidden="true" tabindex="-1"></a></span> 242 - <span id="cb24-3"><a href="#cb24-3" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="kw">mut</span> parser <span class="op">=</span> <span class="pp">Parser::</span>new()<span class="op">;</span></span> 243 - <span id="cb24-4"><a href="#cb24-4" aria-hidden="true" tabindex="-1"></a>parser<span class="op">.</span>set_language(rust_lang)<span class="op">.</span>unwrap()<span class="op">;</span></span> 244 - <span id="cb24-5"><a href="#cb24-5" aria-hidden="true" tabindex="-1"></a></span> 245 - <span id="cb24-6"><a href="#cb24-6" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> parse_tree <span class="op">=</span> parser<span class="op">.</span>parse(<span class="op">&amp;</span>src<span class="op">,</span> <span class="cn">None</span>)<span class="op">.</span>unwrap()<span class="op">;</span></span></code></pre></div> 246 - <p>The second argument to <code>Parser::parse</code> may be of interest. 247 - Tree-sitter has this cool feature that allows for quick reparsing of 248 - existing parse trees if they contain edits. If you do happen to want to 249 - reparse a source file, you can pass in the old tree:</p> 250 - <div class="sourceCode" id="cb25"><pre 251 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb25-1"><a href="#cb25-1" aria-hidden="true" tabindex="-1"></a><span class="co">// if you wish to reparse instead of parse</span></span> 252 - <span id="cb25-2"><a href="#cb25-2" aria-hidden="true" tabindex="-1"></a>old_tree<span class="op">.</span>edit(<span class="co">/* redacted */</span>)<span class="op">;</span></span> 253 - <span id="cb25-3"><a href="#cb25-3" aria-hidden="true" tabindex="-1"></a></span> 254 - <span id="cb25-4"><a href="#cb25-4" aria-hidden="true" tabindex="-1"></a><span class="co">// generate shiny new reparsed tree</span></span> 255 - <span id="cb25-5"><a href="#cb25-5" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> new_tree <span class="op">=</span> parser<span class="op">.</span>parse(<span class="op">&amp;</span>src<span class="op">,</span> <span class="cn">Some</span>(old_tree))<span class="op">.</span>unwrap()</span></code></pre></div> 256 - <p>Anyhow (<a href="http://github.com/dtolnay/anyhow">hah!</a>), now 257 - that we have a parse tree, we can inspect it:</p> 258 - <div class="sourceCode" id="cb26"><pre 259 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb26-1"><a href="#cb26-1" aria-hidden="true" tabindex="-1"></a><span class="pp">println!</span>(<span class="st">&quot;{}&quot;</span><span class="op">,</span> parse_tree<span class="op">.</span>root_node()<span class="op">.</span>to_sexp())<span class="op">;</span></span></code></pre></div> 260 - <p>Or better yet, run a query on it:</p> 261 - <div class="sourceCode" id="cb27"><pre 262 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb27-1"><a href="#cb27-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">tree_sitter::</span>Query<span class="op">;</span></span> 263 - <span id="cb27-2"><a href="#cb27-2" aria-hidden="true" tabindex="-1"></a></span> 264 - <span id="cb27-3"><a href="#cb27-3" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> query <span class="op">=</span> <span class="pp">Query::</span>new(</span> 265 - <span id="cb27-4"><a href="#cb27-4" aria-hidden="true" tabindex="-1"></a> rust_lang<span class="op">,</span></span> 266 - <span id="cb27-5"><a href="#cb27-5" aria-hidden="true" tabindex="-1"></a> <span class="st">r#&quot;</span></span> 267 - <span id="cb27-6"><a href="#cb27-6" aria-hidden="true" tabindex="-1"></a><span class="st"> ((call_expression</span></span> 268 - <span id="cb27-7"><a href="#cb27-7" aria-hidden="true" tabindex="-1"></a><span class="st"> function: (_) @fn-name</span></span> 269 - <span id="cb27-8"><a href="#cb27-8" aria-hidden="true" tabindex="-1"></a><span class="st"> arguments: (arguments (string_literal))) @raise</span></span> 270 - <span id="cb27-9"><a href="#cb27-9" aria-hidden="true" tabindex="-1"></a><span class="st"> (#match? @fn-name &quot;(std::|)env::(var|remove_var)&quot;))</span></span> 271 - <span id="cb27-10"><a href="#cb27-10" aria-hidden="true" tabindex="-1"></a><span class="st"> &quot;#</span></span> 272 - <span id="cb27-11"><a href="#cb27-11" aria-hidden="true" tabindex="-1"></a>)</span> 273 - <span id="cb27-12"><a href="#cb27-12" aria-hidden="true" tabindex="-1"></a><span class="op">.</span>unwrap()<span class="op">;</span></span></code></pre></div> 274 - <p>A <code>QueryCursor</code> is tree-sitter’s way of maintaining state 275 - as we iterate through the matches or captures produced by running a 276 - query on the parse tree. Observe:</p> 277 - <div class="sourceCode" id="cb28"><pre 278 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb28-1"><a href="#cb28-1" aria-hidden="true" tabindex="-1"></a><span class="kw">use</span> <span class="pp">tree_sitter::</span>QueryCursor<span class="op">;</span></span> 279 - <span id="cb28-2"><a href="#cb28-2" aria-hidden="true" tabindex="-1"></a></span> 280 - <span id="cb28-3"><a href="#cb28-3" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> <span class="kw">mut</span> query_cursor <span class="op">=</span> <span class="pp">QueryCursor::</span>new()<span class="op">;</span></span> 281 - <span id="cb28-4"><a href="#cb28-4" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> all_matches <span class="op">=</span> query_cursor<span class="op">.</span>matches(</span> 282 - <span id="cb28-5"><a href="#cb28-5" aria-hidden="true" tabindex="-1"></a> <span class="op">&amp;</span>query<span class="op">,</span></span> 283 - <span id="cb28-6"><a href="#cb28-6" aria-hidden="true" tabindex="-1"></a> parse_tree<span class="op">.</span>root_node()<span class="op">,</span></span> 284 - <span id="cb28-7"><a href="#cb28-7" aria-hidden="true" tabindex="-1"></a> src<span class="op">.</span>as_bytes()<span class="op">,</span></span> 285 - <span id="cb28-8"><a href="#cb28-8" aria-hidden="true" tabindex="-1"></a>)<span class="op">;</span></span></code></pre></div> 286 - <p>We begin by passing our query to the cursor, followed by the “root 287 - node”, which is another way of saying, “start from the top”, and lastly, 288 - the source itself. If you have already taken a look at the C API, you 289 - will notice that the last argument, the source (known as the 290 - <code>TextProvider</code>), is not required. The Rust bindings seem to 291 - require this argument to provide predicate functionality such as 292 - <code>#match?</code> and <code>#eq?</code>.</p> 293 - <p>Do something with the matches:</p> 294 - <div class="sourceCode" id="cb29"><pre 295 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb29-1"><a href="#cb29-1" aria-hidden="true" tabindex="-1"></a><span class="co">// get the index of the capture named &quot;raise&quot;</span></span> 296 - <span id="cb29-2"><a href="#cb29-2" aria-hidden="true" tabindex="-1"></a><span class="kw">let</span> raise_idx <span class="op">=</span> query<span class="op">.</span>capture_index_for_name(<span class="st">&quot;raise&quot;</span>)<span class="op">.</span>unwrap()<span class="op">;</span></span> 297 - <span id="cb29-3"><a href="#cb29-3" aria-hidden="true" tabindex="-1"></a></span> 298 - <span id="cb29-4"><a href="#cb29-4" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> each_match <span class="kw">in</span> all_matches <span class="op">{</span></span> 299 - <span id="cb29-5"><a href="#cb29-5" aria-hidden="true" tabindex="-1"></a> <span class="co">// iterate over all captures called &quot;raise&quot;</span></span> 300 - <span id="cb29-6"><a href="#cb29-6" aria-hidden="true" tabindex="-1"></a> <span class="co">// ignore captures such as &quot;fn-name&quot;</span></span> 301 - <span id="cb29-7"><a href="#cb29-7" aria-hidden="true" tabindex="-1"></a> <span class="cf">for</span> capture <span class="kw">in</span> each_match</span> 302 - <span id="cb29-8"><a href="#cb29-8" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>captures</span> 303 - <span id="cb29-9"><a href="#cb29-9" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>iter()</span> 304 - <span id="cb29-10"><a href="#cb29-10" aria-hidden="true" tabindex="-1"></a> <span class="op">.</span>filter(<span class="op">|</span>c<span class="op">|</span> c<span class="op">.</span>idx <span class="op">==</span> raise_idx)</span> 305 - <span id="cb29-11"><a href="#cb29-11" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span></span> 306 - <span id="cb29-12"><a href="#cb29-12" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> range <span class="op">=</span> capture<span class="op">.</span>node<span class="op">.</span>range()<span class="op">;</span></span> 307 - <span id="cb29-13"><a href="#cb29-13" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> text <span class="op">=</span> <span class="op">&amp;</span>src[range<span class="op">.</span>start_byte<span class="op">..</span>range<span class="op">.</span>end_byte]<span class="op">;</span></span> 308 - <span id="cb29-14"><a href="#cb29-14" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> line <span class="op">=</span> range<span class="op">.</span>start_point<span class="op">.</span>row<span class="op">;</span></span> 309 - <span id="cb29-15"><a href="#cb29-15" aria-hidden="true" tabindex="-1"></a> <span class="kw">let</span> col <span class="op">=</span> range<span class="op">.</span>start_point<span class="op">.</span>column<span class="op">;</span></span> 310 - <span id="cb29-16"><a href="#cb29-16" aria-hidden="true" tabindex="-1"></a> <span class="pp">println!</span>(</span> 311 - <span id="cb29-17"><a href="#cb29-17" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;[Line: {}, Col: {}] Offending source code: `{}`&quot;</span><span class="op">,</span></span> 312 - <span id="cb29-18"><a href="#cb29-18" aria-hidden="true" tabindex="-1"></a> line<span class="op">,</span> col<span class="op">,</span> text</span> 313 - <span id="cb29-19"><a href="#cb29-19" aria-hidden="true" tabindex="-1"></a> )<span class="op">;</span></span> 314 - <span id="cb29-20"><a href="#cb29-20" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 315 - <span id="cb29-21"><a href="#cb29-21" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 316 - <p>Lastly, add the following line to your source code, to get the linter 317 - to catch something:</p> 318 - <div class="sourceCode" id="cb30"><pre 319 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb30-1"><a href="#cb30-1" aria-hidden="true" tabindex="-1"></a><span class="pp">env::</span>remove_var(<span class="st">&quot;RUST_BACKTRACE&quot;</span>)<span class="op">;</span></span></code></pre></div> 320 - <p>And <code>cargo run</code>:</p> 321 - <pre class="shell"><code>λ cargo run 322 - Compiling toy-lint v0.1.0 (/redacted/path/to/toy-lint) 323 - Finished dev [unoptimized + debuginfo] target(s) in 0.74s 324 - Running `target/debug/toy-lint` 325 - [Line: 40, Col: 4] Offending source code: `env::remove_var(&quot;RUST_BACKTRACE&quot;)`</code></pre> 326 - <p>Thank you tree-sitter!</p> 327 - <h3 id="bonus">Bonus</h3> 328 - <p>Keen readers will notice that I avoided 329 - <code>std::env::set_var</code>. Because <code>set_var</code> is called 330 - with two arguments, a “key” and a “value”, unlike <code>env::var</code> 331 - and <code>env::remove_var</code>. As a result, it requires more 332 - juggling:</p> 333 - <div class="sourceCode" id="cb32"><pre 334 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb32-1"><a href="#cb32-1" aria-hidden="true" tabindex="-1"></a>((call_expression</span> 335 - <span id="cb32-2"><a href="#cb32-2" aria-hidden="true" tabindex="-1"></a> function: (<span class="op">_</span>) @fn-name</span> 336 - <span id="cb32-3"><a href="#cb32-3" aria-hidden="true" tabindex="-1"></a> arguments: (arguments <span class="op">.</span> (string_literal)<span class="op">?</span> <span class="op">.</span> (string_literal) <span class="op">.</span>)) @raise</span> 337 - <span id="cb32-4"><a href="#cb32-4" aria-hidden="true" tabindex="-1"></a> (#match? @fn-name <span class="st">&quot;(std::|)env::(var|remove_var|set_var)&quot;</span>))</span></code></pre></div> 338 - <p>The interesting part of this query is the humble <code>.</code>, the 339 - <em>anchor</em> operator. Anchors help constrain child nodes in certain 340 - ways. In this case, it ensures that we match exactly two 341 - <code>string_literal</code>s who are siblings or exactly one 342 - <code>string_literal</code> with no siblings. Unfortunately, this query 343 - also matches the following invalid Rust code:</p> 344 - <div class="sourceCode" id="cb33"><pre 345 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb33-1"><a href="#cb33-1" aria-hidden="true" tabindex="-1"></a><span class="co">// remove_var accepts only 1 arg!</span></span> 346 - <span id="cb33-2"><a href="#cb33-2" aria-hidden="true" tabindex="-1"></a><span class="pp">std::env::</span>remove_var(<span class="st">&quot;RUST_BACKTRACE&quot;</span><span class="op">,</span> <span class="st">&quot;1&quot;</span>)<span class="op">;</span></span></code></pre></div> 347 - <h3 id="notes">Notes</h3> 348 - <p>All-in-all, the query DSL does a great job in lowering the bar to 349 - writing language tools. The knowledge gained from mastering the query 350 - DSL can be applied to other languages that have tree-sitter grammars 351 - too. This query detects <code>to_json</code> methods that do not accept 352 - additional arguments, in Ruby:</p> 353 - <div class="sourceCode" id="cb34"><pre 354 - class="sourceCode scheme"><code class="sourceCode scheme"><span id="cb34-1"><a href="#cb34-1" aria-hidden="true" tabindex="-1"></a>((method</span> 355 - <span id="cb34-2"><a href="#cb34-2" aria-hidden="true" tabindex="-1"></a> name: (identifier) @fn</span> 356 - <span id="cb34-3"><a href="#cb34-3" aria-hidden="true" tabindex="-1"></a> !parameters)</span> 357 - <span id="cb34-4"><a href="#cb34-4" aria-hidden="true" tabindex="-1"></a> (<span class="sc">#i</span>s? @fn <span class="st">&quot;to_json&quot;</span>))</span></code></pre></div> 358 - 359 - </div> 360 - 361 - <div class="intro"> 362 - Hi. 363 - <div class="hot-links"> 364 - <a href="/index.xml" class="feed-button">Subscribe</a> 365 - </div> 366 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 367 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 368 - <p>Reach out at oppili@libera.chat.</p> 369 - </div> 370 - 371 - <a href="/" class="post-end-link">Home</a> 372 - <span>/</span> 373 - <a href="/posts" class="post-end-link">Posts</a> 374 - <span>/</span> 375 - <a class="post-end-link">Lightweight Linting</a> 376 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/lightweight_linting.md 377 - ">View Raw</a> 378 - </div> 379 - </div> 380 - </body> 381 - </html>
-177
docs/posts/lotus58/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Lotus58"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/lotus58"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Lotus58 · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Lotus58</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/lotus58.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 13/06 — 2022 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 53.95 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 4.8 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Lotus58 43 - </h1> 44 - <div class="post-text"> 45 - <p>Earlier this month, I decided that I would laugh at Indian customs in 46 - the face by building a split-ergo mechanical keyboard from scratch 47 - rather than purchasing a Moonlander.</p> 48 - <figure> 49 - <img src="https://u.peppe.rs/i8k.jpg" alt="The finished product" /> 50 - <figcaption aria-hidden="true">The finished product</figcaption> 51 - </figure> 52 - <h2 id="sourcing-the-parts">Sourcing the parts</h2> 53 - <p>If you, like me, live in India, you might find this section useful. 54 - My approach to finding parts:</p> 55 - <ul> 56 - <li>Check reputed, local online stores</li> 57 - <li>Check physical hardware stores</li> 58 - <li>Import the part</li> 59 - </ul> 60 - <h3 id="pcbs">PCBs</h3> 61 - <p>This was by far the hardest component to procure. Fabrication 62 - services have certain <em>capabilities</em>. Capabilities are the 63 - limitations of a fabrication service. For example, a service may be 64 - capable of drilling holes no smaller than 0.3mm in diameter. Most sites 65 - have a verification process to check if their capabilities meet your 66 - design’s requirements. I tried a few local PCB fabrication services:</p> 67 - <ul> 68 - <li>Lion PCB: Capabilities did not meet my requirements</li> 69 - <li>PCBPower: Capabilities did not meet my requirements</li> 70 - <li>Circuitwala: Capabilities did not meet my requirements</li> 71 - </ul> 72 - <p>I settled for JLCPCB, a Chinese service. PCBs themselves were 16 USD, 73 - shipping was another 35 USD, and customs was another 3.5K INR 74 - (ouch).</p> 75 - <h3 id="case-material">Case material</h3> 76 - <p>I don’t really have a case for the Lotus58, it is more of a “plastic 77 - sandwich”. I purchased acrylic plates from Robu. Cheap, fast, solid, and 78 - customizable. I cannot recommend Robu enough. A full set of plates (2 79 - top plates and 2 bottom plates) cost me about 500 INR. I also bought a 80 - pair of laptop height raisers on Amazon to create a budget tenting 81 - setup.</p> 82 - <h3 id="electronics">Electronics</h3> 83 - <p>You’ll need a few rather specific electronic components such as 84 - hotswap sockets and TRRS mounts, the rest are commonly available:</p> 85 - <ul> 86 - <li>Hotswap sockets: StacksKB</li> 87 - <li>TRRS mounts (PJ 320A): StacksKB</li> 88 - <li>Diodes (1N4841): StacksKB</li> 89 - <li>M2 screws: StacksKB</li> 90 - <li>M2 spacers: ThinkRobotics</li> 91 - <li>Arduino Pro Micro: Robu</li> 92 - </ul> 93 - <p>I skimped out on optional components such as OLEDs and rotary 94 - encoders.</p> 95 - <h3 id="switches-and-keycaps">Switches and Keycaps</h3> 96 - <p>Arguably the most fun part of the build:</p> 97 - <ul> 98 - <li>Gateron Oil King switches: Rectangles</li> 99 - <li>DSA blanks: Meckeys</li> 100 - </ul> 101 - <h2 id="building-the-keyboard">Building the keyboard</h2> 102 - <p>The the build is extremely straightforward. Through hole components 103 - are easy to solder. Be wary of component placement and orientation. 104 - Check thrice, solder once. Few debugging tips:</p> 105 - <ul> 106 - <li>if a single key does not actuate, check the hotswap for poor 107 - soldering (reflow the joint), and the switch pins for deformation during 108 - installation</li> 109 - <li>if an entire column or row activates on a single key-press, a 110 - connection has been shorted</li> 111 - <li>if only some of the keys on a given row actuate, a diode has been 112 - soldered on the wrong way</li> 113 - </ul> 114 - <h2 id="the-typing-experience">The typing experience</h2> 115 - <p>I decidede to give QWERTY the boot and learn Colemak along with the 116 - new keyboard. The first few weeks were terrible because I could neither 117 - type QWERTY nor Colemak, but I got the hang of it pretty quickly. Typing 118 - websites do help, but it is best to simply use it in your daily 119 - workflow. No site can help you get accustomed to the various things you 120 - use your keyboard for such as switching windows or navigating 121 - vim/tmux.</p> 122 - <h3 id="colemak">Colemak</h3> 123 - <p>Alt layouts such as Colemak are definitely worth it. I find that 124 - Colemak reduces finger movement a lot, a good portion of the keys on the 125 - left hand are the same as QWERTY, it is fairly easy to pick up as 126 - well.</p> 127 - <h3 id="vim">Vim</h3> 128 - <p>Using an alt layout means most programs with keyboard shortcuts are 129 - not going to work as expected, <code>HJKL</code> on vim for movements 130 - being one of them. I took the short route out by creating a new layer 131 - with arrow keys on the home row:</p> 132 - <pre><code>default homerow: 133 - H N E I 134 - 135 - &quot;nav&quot; layer: 136 - &lt; v ^ &gt;</code></pre> 137 - <p>The remaining commands in vim are largely mnemonics. Navigating with 138 - home-row arrow keys also means that I can use “HJKL” globally, to scroll 139 - a website, for example.</p> 140 - <h3 id="cutting-down-to-34-keys">Cutting down to 34 keys</h3> 141 - <p>A couple months into my ergo journey, I realized that I could get 142 - away by moving my fingers even lesser. I moved modifiers such as 143 - <code>Super</code>, <code>Ctrl</code>, <code>Alt</code> and 144 - <code>Shift</code> to the home-row as QMK Mod Taps. The rest of the keys 145 - are cleverly placed in layers, not too much unlike the Miryoku layout. 146 - Even for someone that writes Rust (a symbol-heavy grammar), I find 147 - 34-keys to be sufficient.</p> 148 - <h2 id="conclusion">Conclusion</h2> 149 - <p>I have been bitten by the ergomech bug.</p> 150 - <figure> 151 - <img src="https://u.peppe.rs/XM3.jpg" alt="The Lotus58 in action" /> 152 - <figcaption aria-hidden="true">The Lotus58 in action</figcaption> 153 - </figure> 154 - 155 - </div> 156 - 157 - <div class="intro"> 158 - Hi. 159 - <div class="hot-links"> 160 - <a href="/index.xml" class="feed-button">Subscribe</a> 161 - </div> 162 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 163 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 164 - <p>Reach out at oppili@libera.chat.</p> 165 - </div> 166 - 167 - <a href="/" class="post-end-link">Home</a> 168 - <span>/</span> 169 - <a href="/posts" class="post-end-link">Posts</a> 170 - <span>/</span> 171 - <a class="post-end-link">Lotus58</a> 172 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/lotus58.md 173 - ">View Raw</a> 174 - </div> 175 - </div> 176 - </body> 177 - </html>
-196
docs/posts/mounting_the_atmosphere/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Mounting The Atmosphere"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/mounting_the_atmosphere"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Mounting The Atmosphere · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Mounting The Atmosphere</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/mounting_the_atmosphere.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 31/07 — 2025 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 60.34 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 4.3 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Mounting The Atmosphere 43 - </h1> 44 - <div class="post-text"> 45 - <p><a href="https://tangled.sh/@oppi.li/pdsfs">pdsfs</a> is a tool that 46 - mounts <a href="https://atproto.com">atproto</a> PDS repositories as a 47 - <a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace">FUSE</a> 48 - filesystem.</p> 49 - <p>A <a href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace">PDS 50 - repository</a> contains all data published by a user to the atmosphere. 51 - It is exportable as a CAR (content-addressable archive) file. pdsfs is a 52 - tool that mounts this CAR file as a readonly-FUSE filesystem, allowing 53 - quick and easy exploration.</p> 54 - <p>To motivate the need for such a program, we could begin by mounting a 55 - repository:</p> 56 - <pre><code>λ pdsfs oppi.li 57 - mounted at &quot;mnt&quot; 58 - hit enter to unmount and exit...</code></pre> 59 - <p>oppi.li is my handle in the atmosphere. The tool does some hardwork 60 - to determine the location of my PDS repository given my handle, but that 61 - is not important. Let’s have a look around:</p> 62 - <pre><code>λ ls mnt/ 63 - did:plc:qfpnj4og54vl56wngdriaxug/</code></pre> 64 - <p>The <code>did:plc:stuff</code> is my <a 65 - href="https://en.wikipedia.org/wiki/Filesystem_in_Userspace">DID</a>. 66 - Digging deeper:</p> 67 - <pre><code>λ ls mnt/did\:plc\:qfpnj4og54vl56wngdriaxug/ 68 - app.bsky.actor.profile/ place.stream.chat.message/ 69 - app.bsky.actor.status/ place.stream.chat.profile/ 70 - app.bsky.feed.generator/ place.stream.key/ 71 - app.bsky.feed.like/ place.stream.livestream/ 72 - app.bsky.feed.post/ sh.tangled.actor.profile/ 73 - app.bsky.feed.repost/ sh.tangled.feed.reaction/ 74 - app.bsky.graph.block/ sh.tangled.feed.star/ 75 - app.bsky.graph.follow/ sh.tangled.graph.follow/ 76 - app.rocksky.album/ sh.tangled.knot/ 77 - app.rocksky.artist/ sh.tangled.knot.member/ 78 - . 79 - . 80 - .</code></pre> 81 - <p>We have some data from the repository now. These are “collections”. 82 - If I want to publish a post to Bluesky, I would write content to the 83 - <code>app.bsky.feed.post</code> collection in my PDS. This will then be 84 - indexed by a Bluesky appview (such as <a 85 - href="https://bsky.app">bsky.app</a> or <a 86 - href="https://zeppelin.social">zeppelin.social</a>) and show up under my 87 - profile there.</p> 88 - <p>pdsfs is kind enough to deserialize the in the PDS repository (stored 89 - as CBOR normally) to JSON on the filesystem:</p> 90 - <pre><code>λ cat sh.tangled.repo/3ljidbevrjh22 | jq 91 - { 92 - &quot;$type&quot;: &quot;sh.tangled.repo&quot;, 93 - &quot;addedAt&quot;: &quot;2025-03-03T16:04:13Z&quot;, 94 - &quot;knot&quot;: &quot;knot1.tangled.sh&quot;, 95 - &quot;name&quot;: &quot;hello-world&quot;, 96 - &quot;owner&quot;: &quot;did:plc:3danwc67lo7obz2fmdg6jxcr&quot; 97 - }</code></pre> 98 - <p>Thanks pdsfs!</p> 99 - <p>I publish my music listening habits to my PDS to the 100 - <code>app.rocksky.scrobble</code> collection, because <a 101 - href="https://rocksky.app">Rocksky</a> recognizes and indexes this 102 - collection. I have wired up my personal navidrome instance to write data 103 - of this form into my PDS everytime I listen to a track. Here are my top 104 - artists in order:</p> 105 - <pre><code>λ jq -r &#39;.artist&#39; app.rocksky.scrobble/* | sort | uniq -c | sort -nr 106 - 117 Thank You Scientist 107 - 45 FKJ 108 - 34 Covet 109 - 33 VOLA 110 - 23 Sam Cooke 111 - 22 Dark Tranquillity 112 - 21 Piero Piccioni 113 - 12 Bloodywood 114 - 11 Frank Sinatra 115 - 10 Dream Theater</code></pre> 116 - <p>It is true, I love Sam Cooke.</p> 117 - <p>pdsfs allows mounting multiple repositories at a time. Allow me to 118 - introduce my friends:</p> 119 - <pre><code>λ pdsfs icyphox.sh anil.recoil.org steveklabnik.com tangled.sh 120 - using cached CAR file for...did:plc:hwevmowznbiukdf6uk5dwrrq 121 - using cached CAR file for...did:plc:nhyitepp3u4u6fcfboegzcjw 122 - download complete for...did:plc:3danwc67lo7obz2fmdg6jxcr 123 - download complete for...did:plc:wshs7t2adsemcrrd4snkeqli 124 - mounted at &quot;mnt&quot; 125 - hit enter to unmount and exit... 126 - 127 - # -- in a separate shell -- 128 - 129 - λ cat ./mnt/*/app.bsky.actor.profile/* \ 130 - | jq -r &#39;&quot;\(.displayName)\n\(.description)\n---&quot;&#39; \ 131 - | sed &#39;/^$/d&#39; 132 - Steve Klabnik 133 - #rustlang, #jj-vcs, atproto, shitposts, urbanism. I 134 - contain multitudes. Working on #ruelang but just for 135 - fun. Currently in Austin, TX, but from Pittsburgh. 136 - Previously in Bushwick, the Mission, LA. 137 - --- 138 - Anirudh Oppiliappan 139 - building @tangled.sh — code collaboration platform built 140 - on atproto helsinki, finland · https://anirudh.fi · 141 - (somewhat) effective altruist 142 - --- 143 - Anil Madhavapeddy 144 - Professor of Planetary Computing at the University of 145 - Cambridge @cst.cam.ac.uk, where I co-lead the 146 - @eeg.cl.cam.ac.uk, and am also to found at 147 - @conservation.cam.ac.uk. Homepage at 148 - https://anil.recoil.org 149 - --- 150 - Tangled 151 - https://tangled.sh is a git collaboration platform built 152 - on atproto. Social coding, but for real this time! 153 - Discord: chat.tangled.sh IRC: #tangled @ libera.chat 154 - Built by @oppi.li &amp; @icyphox.sh 155 - ---</code></pre> 156 - <p>All my friends use <a href="https://tangled.sh">tangled.sh</a>, which 157 - requires them to publish their ssh public key to their PDii (PDSes?). 158 - Perhaps I would like to add their keys to my allowed_signers file to 159 - verify their commit signatures:</p> 160 - <pre><code>λ for dir in ./*/sh.tangled.publicKey; 161 - do cat $dir/$(ls -r $dir | head -n1) | jq -r &#39;.key&#39;; 162 - done | tee allowed_signers 163 - ssh-rsa AAAAB3NzaC1yc2EAAA...dHPqc= steveklabnik@DESKTOP-VV370NK 164 - ssh-ed25519 AAAAC3NzaC1lZD...g9bAdk icy@wyndle 165 - ssh-ed25519 AAAAC3NzaC1lZD...BqlM1u anil@recoil.org</code></pre> 166 - <p>FUSE is quite liberating in that it allows you to represent anything 167 - as a filesystem. When applications like <code>ls</code> and 168 - <code>cat</code> are executed, the syscalls to <code>open</code> and 169 - <code>read</code> are rerouted to your custom fs implementation (pdsfs 170 - in this case). The custom fs implementation is free to as it pleases, in 171 - fact the first iteration of pdsfs accessed the network for each 172 - open/read call to fetch live data from PDii.</p> 173 - 174 - </div> 175 - 176 - <div class="intro"> 177 - Hi. 178 - <div class="hot-links"> 179 - <a href="/index.xml" class="feed-button">Subscribe</a> 180 - </div> 181 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 182 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 183 - <p>Reach out at oppili@libera.chat.</p> 184 - </div> 185 - 186 - <a href="/" class="post-end-link">Home</a> 187 - <span>/</span> 188 - <a href="/posts" class="post-end-link">Posts</a> 189 - <span>/</span> 190 - <a class="post-end-link">Mounting The Atmosphere</a> 191 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/mounting_the_atmosphere.md 192 - ">View Raw</a> 193 - </div> 194 - </div> 195 - </body> 196 - </html>
-91
docs/posts/my_setup/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="My Setup"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/my_setup"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>My Setup · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">My Setup</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/my_setup.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 06/11 — 2019 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 10.39 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 1.0 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - My Setup 43 - </h1> 44 - <div class="post-text"> 45 - <p>Decided to do one of these because everyone does one of these.</p> 46 - <p><img src="https://u.peppe.rs/Hb.png" /></p> 47 - <p>My entire setup is managed with GNU <code>stow</code>, making it 48 - easier to replicate on fresh installations. You can find my 49 - configuration files on <a 50 - href="https://github.com/nerdypepper">GitHub</a>.</p> 51 - <p>I run Void Linux (glibc) on my <a 52 - href="https://store.hp.com/us/en/mdp/laptops/envy-13">HP Envy 13” 53 - (2018)</a>. To keep things simple, I run a raw X session with 54 - <code>2bwm</code> as my window manager, along with <code>dunst</code> 55 - (notification daemon) and Sam’s <a 56 - href="https://github.com/sdhand/compton"><code>compton</code></a> 57 - (compositor) fork.</p> 58 - <p>I am a fan of GNU tools, so I use <code>bash</code> as my shell, and 59 - <code>coreutils</code> to manage files, archives, strings, paths etc. I 60 - edit files with <code>vim</code>, chat with <code>weechat</code>, listen 61 - to music with <code>cmus</code>, monitor processes with 62 - <code>htop</code>, manage sessions with <code>tmux</code>, read pdfs in 63 - <code>zathura</code>. I rarely ever leave the comfort of my terminal 64 - emulator, <code>urxvt</code>.</p> 65 - <p>Most of my academic typesetting is done with TeX, and compiled with 66 - <code>xelatex</code>. Other <em>fun</em> documents are made with GIMP 67 - :).</p> 68 - 69 - </div> 70 - 71 - <div class="intro"> 72 - Hi. 73 - <div class="hot-links"> 74 - <a href="/index.xml" class="feed-button">Subscribe</a> 75 - </div> 76 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 77 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 78 - <p>Reach out at oppili@libera.chat.</p> 79 - </div> 80 - 81 - <a href="/" class="post-end-link">Home</a> 82 - <span>/</span> 83 - <a href="/posts" class="post-end-link">Posts</a> 84 - <span>/</span> 85 - <a class="post-end-link">My Setup</a> 86 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/my_setup.md 87 - ">View Raw</a> 88 - </div> 89 - </div> 90 - </body> 91 - </html>
-140
docs/posts/nixOS/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="NixOS"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/nixOS"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>NixOS · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">NixOS</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/nixOS.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 01/09 — 2020 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 37.56 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 3.4 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - NixOS 43 - </h1> 44 - <div class="post-text"> 45 - <p>I have been eyeing operating systems with functional package managers 46 - for a while now, aka, NixOS or Guix. Reproducible builds, declarative 47 - and rollback-able system configuration, system consistency, all sound 48 - pretty cool. I have been using NixOS for about a month now.</p> 49 - <h3 id="installation">Installation</h3> 50 - <p>I went with their minimal installation ISO. The installation was 51 - pretty smooth from start to end, no hitches there. The entire <a 52 - href="https://nixos.org/manual/nixos/stable/">manual</a> is available 53 - offline, and is accessible during the installation. Very handy.</p> 54 - <h3 id="setup">Setup</h3> 55 - <p>The entire system is configured via 56 - <code>/etc/nixos/configuration.nix</code>. Wifi, <code>libinput</code> 57 - gestures, audio, locale settings, there are options for literally 58 - everything. You can declaratively write down the packages you want 59 - installed too. With fresh installs of most distros, I usually fumble 60 - with getting things like screen backlight and media keys to work. If I 61 - do manage to fix it, I can’t carry it forward to future installations 62 - trivially. Getting all my hardware to work on NixOS is as easy as:</p> 63 - <pre><code>{ 64 - server.xserver.libinput.enable = true; # touchpad 65 - programs.light.enable = true; # backlight 66 - hardware.pulseaudio.enable = true; # audio 67 - networking.wireless.enable = true; # wifi 68 - }</code></pre> 69 - <h3 id="developing-with-nix">Developing with Nix</h3> 70 - <p>Nix makes it easy to enter environments that aren’t affected by your 71 - system configuration using <code>nix-shell</code>.</p> 72 - <p>Builds may be generated by specifying a <code>default.nix</code> 73 - file, and running <code>nix-build</code>. Conventional package managers 74 - require you to specify a dependency list, but there is no guarantee that 75 - this list is complete. The package will build on your machine even if 76 - you forget a dependency. However, with Nix, packages are installed to 77 - <code>/nix/store</code>, and not global paths such as 78 - <code>/usr/bin/...</code>, if your project builds, it means you have 79 - included every last one.</p> 80 - <p>Issues on most my projects have been “unable to build because 81 - <code>libxcb</code> is missing”, or “this version of 82 - <code>openssl</code> is too old”. Tools like <code>cargo</code> and 83 - <code>pip</code> are poor package managers. While they <em>can</em> 84 - guarantee that Rust or Python dependencies are met, they make 85 - assumptions about the target system.</p> 86 - <p>For example, <a href="https://github.com/nerdypepper/site">this 87 - website</a> is now built using Nix, anyone using Nix may simply, clone 88 - the repository and run <code>./generate.sh</code>, and it would <em>just 89 - work</em>, while keeping your global namespace clean™:</p> 90 - <div class="sourceCode" id="cb2"><pre 91 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co">#! /usr/bin/env nix-shell</span></span> 92 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="co">#! nix-shell -i bash -p eva pandoc esh</span></span> 93 - <span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span> 94 - <span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="co"># some bash magic ;)</span></span></code></pre></div> 95 - <p>Dependencies are included with the <code>-p</code> flag, the shell 96 - script is executed with an interpreter, specified with the 97 - <code>-i</code> flag.</p> 98 - <h3 id="impressions">Impressions</h3> 99 - <p>NixOS is by no means, simple. As a newcomer, using Nix was not easy, 100 - heck, I had to learn a purely functional, lazy language to just build 101 - programs. There is a lot to be desired on the tooling front as well. A 102 - well fleshed out LSP plugin would be nice (<a 103 - href="https://github.com/nix-community/rnix-lsp">rnix-lsp looks 104 - promising</a>).</p> 105 - <p>Being able to rollback changes at a system level is cool. Package 106 - broke something? Just <code>nixos-rebuild switch --rollback</code>! 107 - Deleted <code>nix</code> by mistake? Find the binary in 108 - <code>/nix/store</code> and rollback! You aren’t punished for not 109 - thinking twice.</p> 110 - <p>I don’t see myself switching to anything else in the near future, 111 - NixOS does a lot of things right. If I ever need to reinstall NixOS, I 112 - can generate an <a 113 - href="https://github.com/nix-community/nixos-generators">image of my 114 - current system</a>.</p> 115 - <p><a href="https://u.peppe.rs/6m.png"><img 116 - src="https://u.peppe.rs/6m.png" /></a></p> 117 - 118 - </div> 119 - 120 - <div class="intro"> 121 - Hi. 122 - <div class="hot-links"> 123 - <a href="/index.xml" class="feed-button">Subscribe</a> 124 - </div> 125 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 126 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 127 - <p>Reach out at oppili@libera.chat.</p> 128 - </div> 129 - 130 - <a href="/" class="post-end-link">Home</a> 131 - <span>/</span> 132 - <a href="/posts" class="post-end-link">Posts</a> 133 - <span>/</span> 134 - <a class="post-end-link">NixOS</a> 135 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/nixOS.md 136 - ">View Raw</a> 137 - </div> 138 - </div> 139 - </body> 140 - </html>
-259
docs/posts/novice_nix:_flake_templates/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Novice Nix: Flake Templates"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/novice_nix:_flake_templates"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Novice Nix: Flake Templates · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Novice Nix: Flake Templates</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/novice_nix:_flake_templates.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 05/10 — 2021 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 91.51 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 5.5 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Novice Nix: Flake Templates 43 - </h1> 44 - <div class="post-text"> 45 - <p>Flakes are very handy to setup entirely pure, project-specific 46 - dependencies (not just dependencies, but build steps, shell environments 47 - and more) in a declarative way. Writing Flake expressions can get 48 - repetitive though, oftentimes, you’d much rather start off with a 49 - skeleton. Luckily, <code>nix</code> already supports templates!</p> 50 - <p>You might already be familiar with <code>nix flake init</code>, that 51 - drops a “default” flake expression into your current working directory. 52 - If you head over to the manpage:</p> 53 - <div class="sourceCode" id="cb1"><pre 54 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">nix</span> flake init <span class="at">--help</span></span></code></pre></div> 55 - <p>You will read that <code>nix flake init</code> creates a flake using 56 - the “default template”. Additionally, you can create a flake from a 57 - specific template by passing the <code>-t</code> flag. Where does this 58 - default originate from?</p> 59 - <h2 id="flake-registries">Flake Registries</h2> 60 - <p>Quick detour into registries! Registries are a way to alias popular 61 - flakes using identifiers:</p> 62 - <div class="sourceCode" id="cb2"><pre 63 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co"># list a few predefined registries</span></span> 64 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix registry list</span> 65 - <span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="bu">.</span> . . </span> 66 - <span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="ex">global</span> flake:nixpkgs github:NixOS/nixpkgs</span> 67 - <span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="ex">global</span> flake:patchelf github:NixOS/patchelf</span> 68 - <span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="ex">global</span> flake:nix-serve github:edolstra/nix-serve</span> 69 - <span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a><span class="ex">global</span> flake:templates github:NixOS/templates</span> 70 - <span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="ex">global</span> flake:nickel github:tweag/nickel</span> 71 - <span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="bu">.</span> . .</span> 72 - <span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a></span> 73 - <span id="cb2-11"><a href="#cb2-11" aria-hidden="true" tabindex="-1"></a><span class="co"># you can do </span></span> 74 - <span id="cb2-12"><a href="#cb2-12" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake show nickel</span> 75 - <span id="cb2-13"><a href="#cb2-13" aria-hidden="true" tabindex="-1"></a></span> 76 - <span id="cb2-14"><a href="#cb2-14" aria-hidden="true" tabindex="-1"></a><span class="co"># instead of </span></span> 77 - <span id="cb2-15"><a href="#cb2-15" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake show github:tweag/nickel</span> 78 - <span id="cb2-16"><a href="#cb2-16" aria-hidden="true" tabindex="-1"></a></span> 79 - <span id="cb2-17"><a href="#cb2-17" aria-hidden="true" tabindex="-1"></a><span class="co"># which is short for</span></span> 80 - <span id="cb2-18"><a href="#cb2-18" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake show git+https://github.com/tweag/nickel</span></code></pre></div> 81 - <p>You might notice a registry called <code>templates</code> aliased to 82 - <code>github:NixOS/templates</code>. Take a peek with 83 - <code>nix flake show</code>:</p> 84 - <div class="sourceCode" id="cb3"><pre 85 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake show templates</span> 86 - <span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ex">github:NixOS/templates/79f48a7b822f35c068c5e235da2e9fbd154cecee</span></span> 87 - <span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="ex">├───defaultTemplate:</span> template: A very basic flake</span> 88 - <span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="ex">└───templates</span></span> 89 - <span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">├───bash-hello:</span> template: An over-engineered Hello World in bash</span> 90 - <span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">├───c-hello:</span> template: An over-engineered Hello World in C</span> 91 - <span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">├───rust-web-server:</span> template: A Rust web server including a NixOS module</span> 92 - <span id="cb3-8"><a href="#cb3-8" aria-hidden="true" tabindex="-1"></a> <span class="ex">├───simpleContainer:</span> template: A NixOS container running apache-httpd</span> 93 - <span id="cb3-9"><a href="#cb3-9" aria-hidden="true" tabindex="-1"></a> <span class="ex">└───trivial:</span> template: A very basic flake</span></code></pre></div> 94 - <p>Aha! There is a flake output called <code>defaultTemplate</code>. 95 - This is the template being sourced when you run 96 - <code>nix flake init</code>. Astute readers may conclude the 97 - following:</p> 98 - <div class="sourceCode" id="cb4"><pre 99 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake init</span> 100 - <span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a></span> 101 - <span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="co"># is equivalent to</span></span> 102 - <span id="cb4-4"><a href="#cb4-4" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake init <span class="at">-t</span> templates#defaultTemplate</span> 103 - <span id="cb4-5"><a href="#cb4-5" aria-hidden="true" tabindex="-1"></a></span> 104 - <span id="cb4-6"><a href="#cb4-6" aria-hidden="true" tabindex="-1"></a><span class="co"># is equivalent to</span></span> 105 - <span id="cb4-7"><a href="#cb4-7" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake init <span class="at">-t</span> github:NixOS/templates#defaultTemplate</span> 106 - <span id="cb4-8"><a href="#cb4-8" aria-hidden="true" tabindex="-1"></a></span> 107 - <span id="cb4-9"><a href="#cb4-9" aria-hidden="true" tabindex="-1"></a><span class="co"># which is short for</span></span> 108 - <span id="cb4-10"><a href="#cb4-10" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake init <span class="at">-t</span> git+https://github.com/NixOS/templates#defaultTemplate</span></code></pre></div> 109 - <p>Similarly, the other templates can be accessed via:</p> 110 - <div class="sourceCode" id="cb5"><pre 111 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake init <span class="at">-t</span> templates#c-hello</span> 112 - <span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake init <span class="at">-t</span> templates#simpleContainer</span> 113 - <span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="co"># I think you get the drift ...</span></span></code></pre></div> 114 - <h2 id="rolling-your-own-templates">Rolling your own templates</h2> 115 - <p>Alright, so all we need to do is:</p> 116 - <ul> 117 - <li>create a flake with a <code>templates</code> output</li> 118 - <li>populate our template directories with content</li> 119 - <li>(<strong>optionally</strong>) alias our custom templates flake to an 120 - identifier using registries, for easier access</li> 121 - </ul> 122 - <p>Start off by creating a directory to store your templates in (we will 123 - be converting this to a registry later):</p> 124 - <div class="sourceCode" id="cb6"><pre 125 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> mkdir ~/mytemplates</span></code></pre></div> 126 - <p>A flake that exposes a “template” as its output looks something like 127 - this:</p> 128 - <div class="sourceCode" id="cb7"><pre 129 - class="sourceCode nix"><code class="sourceCode nix"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="co"># inside ~/mytemplates/flake.nix</span></span> 130 - <span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a></span> 131 - <span id="cb7-3"><a href="#cb7-3" aria-hidden="true" tabindex="-1"></a><span class="op">{</span></span> 132 - <span id="cb7-4"><a href="#cb7-4" aria-hidden="true" tabindex="-1"></a> <span class="va">description</span> <span class="op">=</span> <span class="st">&quot;Pepper&#39;s flake templates&quot;</span><span class="op">;</span></span> 133 - <span id="cb7-5"><a href="#cb7-5" aria-hidden="true" tabindex="-1"></a></span> 134 - <span id="cb7-6"><a href="#cb7-6" aria-hidden="true" tabindex="-1"></a> <span class="va">outputs</span> <span class="op">=</span> <span class="op">{</span> <span class="va">self</span><span class="op">,</span> <span class="op">...</span> <span class="op">}</span>: <span class="op">{</span></span> 135 - <span id="cb7-7"><a href="#cb7-7" aria-hidden="true" tabindex="-1"></a> <span class="va">templates</span> <span class="op">=</span> <span class="op">{</span></span> 136 - <span id="cb7-8"><a href="#cb7-8" aria-hidden="true" tabindex="-1"></a> <span class="va">latex-report</span> <span class="op">=</span> <span class="op">{</span></span> 137 - <span id="cb7-9"><a href="#cb7-9" aria-hidden="true" tabindex="-1"></a> <span class="va">path</span> <span class="op">=</span> <span class="ss">./latex-report-template</span><span class="op">;</span></span> 138 - <span id="cb7-10"><a href="#cb7-10" aria-hidden="true" tabindex="-1"></a> <span class="va">description</span> <span class="op">=</span> <span class="st">&quot;A latex whitepaper project&quot;</span><span class="op">;</span></span> 139 - <span id="cb7-11"><a href="#cb7-11" aria-hidden="true" tabindex="-1"></a> <span class="op">};</span></span> 140 - <span id="cb7-12"><a href="#cb7-12" aria-hidden="true" tabindex="-1"></a> <span class="va">rust-hello</span> <span class="op">=</span> <span class="op">{</span></span> 141 - <span id="cb7-13"><a href="#cb7-13" aria-hidden="true" tabindex="-1"></a> <span class="va">path</span> <span class="op">=</span> <span class="ss">./rust-hello-template</span><span class="op">;</span></span> 142 - <span id="cb7-14"><a href="#cb7-14" aria-hidden="true" tabindex="-1"></a> <span class="va">description</span> <span class="op">=</span> <span class="st">&quot;Simple Hello World in Rust&quot;</span><span class="op">;</span></span> 143 - <span id="cb7-15"><a href="#cb7-15" aria-hidden="true" tabindex="-1"></a> <span class="op">};</span></span> 144 - <span id="cb7-16"><a href="#cb7-16" aria-hidden="true" tabindex="-1"></a> <span class="op">};</span></span> 145 - <span id="cb7-17"><a href="#cb7-17" aria-hidden="true" tabindex="-1"></a> <span class="op">};</span></span> 146 - <span id="cb7-18"><a href="#cb7-18" aria-hidden="true" tabindex="-1"></a><span class="op">}</span></span></code></pre></div> 147 - <p>The <code>path</code> attribute to each template is what gets copied 148 - over when you initialize a flake. Running 149 - <code>nix flake init -t .#latex-report</code> will initialize the 150 - current directory with the contents of 151 - <code>./latex-report-template</code> (we are yet to populate these 152 - directories).</p> 153 - <p>The output of <code>nix flake show</code> should be something 154 - like:</p> 155 - <div class="sourceCode" id="cb8"><pre 156 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb8-1"><a href="#cb8-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake show</span> 157 - <span id="cb8-2"><a href="#cb8-2" aria-hidden="true" tabindex="-1"></a><span class="ex">path:/home/np/code/nix-stuff/template-tests?narHash=sha256-{...}</span></span> 158 - <span id="cb8-3"><a href="#cb8-3" aria-hidden="true" tabindex="-1"></a><span class="ex">└───templates</span></span> 159 - <span id="cb8-4"><a href="#cb8-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">├───latex-report:</span> template: A latex whitepaper project</span> 160 - <span id="cb8-5"><a href="#cb8-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">└───rust-hello:</span> template: Simple Hello World in Rust</span></code></pre></div> 161 - <p>Populate your template directories with content, here are my template 162 - directories for example:</p> 163 - <div class="sourceCode" id="cb9"><pre 164 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb9-1"><a href="#cb9-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> tree mytemplates</span> 165 - <span id="cb9-2"><a href="#cb9-2" aria-hidden="true" tabindex="-1"></a><span class="ex">mytemplates/</span></span> 166 - <span id="cb9-3"><a href="#cb9-3" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> flake.nix</span> 167 - <span id="cb9-4"><a href="#cb9-4" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> latex-report-template</span> 168 - <span id="cb9-5"><a href="#cb9-5" aria-hidden="true" tabindex="-1"></a><span class="ex">│  </span> ├── flake.nix</span> 169 - <span id="cb9-6"><a href="#cb9-6" aria-hidden="true" tabindex="-1"></a><span class="ex">│  </span> ├── makefile</span> 170 - <span id="cb9-7"><a href="#cb9-7" aria-hidden="true" tabindex="-1"></a><span class="ex">│  </span> └── src</span> 171 - <span id="cb9-8"><a href="#cb9-8" aria-hidden="true" tabindex="-1"></a><span class="ex">│  </span> ├── meta.sty</span> 172 - <span id="cb9-9"><a href="#cb9-9" aria-hidden="true" tabindex="-1"></a><span class="ex">│  </span> └── report.tex</span> 173 - <span id="cb9-10"><a href="#cb9-10" aria-hidden="true" tabindex="-1"></a><span class="ex">└──</span> rust-hello-template</span> 174 - <span id="cb9-11"><a href="#cb9-11" aria-hidden="true" tabindex="-1"></a> <span class="ex">├──</span> Cargo.toml</span> 175 - <span id="cb9-12"><a href="#cb9-12" aria-hidden="true" tabindex="-1"></a> <span class="ex">├──</span> flake.nix</span> 176 - <span id="cb9-13"><a href="#cb9-13" aria-hidden="true" tabindex="-1"></a> <span class="ex">└──</span> src</span> 177 - <span id="cb9-14"><a href="#cb9-14" aria-hidden="true" tabindex="-1"></a> <span class="ex">└──</span> main.rs</span></code></pre></div> 178 - <p>And that’s it! Start using your templates with:</p> 179 - <div class="sourceCode" id="cb10"><pre 180 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb10-1"><a href="#cb10-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake init <span class="at">-t</span> ~/mytemplates#rust-hello</span> 181 - <span id="cb10-2"><a href="#cb10-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> tree .</span> 182 - <span id="cb10-3"><a href="#cb10-3" aria-hidden="true" tabindex="-1"></a><span class="bu">.</span></span> 183 - <span id="cb10-4"><a href="#cb10-4" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> Cargo.toml</span> 184 - <span id="cb10-5"><a href="#cb10-5" aria-hidden="true" tabindex="-1"></a><span class="ex">├──</span> flake.nix</span> 185 - <span id="cb10-6"><a href="#cb10-6" aria-hidden="true" tabindex="-1"></a><span class="ex">└──</span> src</span> 186 - <span id="cb10-7"><a href="#cb10-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">└──</span> main.rs</span></code></pre></div> 187 - <p>To avoid writing <code>~/mytemplates</code> each time, simply alias 188 - it to a registry:</p> 189 - <div class="sourceCode" id="cb11"><pre 190 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb11-1"><a href="#cb11-1" aria-hidden="true" tabindex="-1"></a><span class="co"># alias it to `biscuits`</span></span> 191 - <span id="cb11-2"><a href="#cb11-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix registry add biscuits ~/mytemplates</span> 192 - <span id="cb11-3"><a href="#cb11-3" aria-hidden="true" tabindex="-1"></a></span> 193 - <span id="cb11-4"><a href="#cb11-4" aria-hidden="true" tabindex="-1"></a><span class="co"># you will see it listed under `user` registries</span></span> 194 - <span id="cb11-5"><a href="#cb11-5" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix registry list</span> 195 - <span id="cb11-6"><a href="#cb11-6" aria-hidden="true" tabindex="-1"></a><span class="bu">.</span> . .</span> 196 - <span id="cb11-7"><a href="#cb11-7" aria-hidden="true" tabindex="-1"></a><span class="ex">user</span> flake:biscuits path:/home/np/template-tests</span> 197 - <span id="cb11-8"><a href="#cb11-8" aria-hidden="true" tabindex="-1"></a><span class="bu">.</span> . .</span> 198 - <span id="cb11-9"><a href="#cb11-9" aria-hidden="true" tabindex="-1"></a></span> 199 - <span id="cb11-10"><a href="#cb11-10" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> nix flake init <span class="at">-t</span> biscuits#latex-report</span></code></pre></div> 200 - <h2 id="extending-the-official-templates">Extending the official 201 - templates</h2> 202 - <p>I personally, would like the <code>biscuits</code> registry to 203 - include not just my homemade templates, but also the templates from 204 - <code>NixOS/templates</code> (and maybe a couple of other repositories 205 - in the wild):</p> 206 - <div class="sourceCode" id="cb12"><pre 207 - class="sourceCode nix"><code class="sourceCode nix"><span id="cb12-1"><a href="#cb12-1" aria-hidden="true" tabindex="-1"></a> <span class="op">{</span></span> 208 - <span id="cb12-2"><a href="#cb12-2" aria-hidden="true" tabindex="-1"></a> <span class="va">description</span> <span class="op">=</span> <span class="st">&quot;Pepper&#39;s flake templates&quot;</span><span class="op">;</span></span> 209 - <span id="cb12-3"><a href="#cb12-3" aria-hidden="true" tabindex="-1"></a> </span> 210 - <span id="cb12-4"><a href="#cb12-4" aria-hidden="true" tabindex="-1"></a>+ <span class="va">inputs</span> <span class="op">=</span> <span class="op">{</span></span> 211 - <span id="cb12-5"><a href="#cb12-5" aria-hidden="true" tabindex="-1"></a>+ <span class="va">official-templates</span>.<span class="va">url</span> <span class="op">=</span> <span class="va">github</span><span class="op">:</span><span class="ss">NixOS/templates</span><span class="op">;</span></span> 212 - <span id="cb12-6"><a href="#cb12-6" aria-hidden="true" tabindex="-1"></a>+ <span class="va">other-templates</span>.<span class="va">url</span> <span class="op">=</span> <span class="va">github</span><span class="op">:</span><span class="ss">some-other/templates</span><span class="op">;</span></span> 213 - <span id="cb12-7"><a href="#cb12-7" aria-hidden="true" tabindex="-1"></a>+ <span class="op">};</span></span> 214 - <span id="cb12-8"><a href="#cb12-8" aria-hidden="true" tabindex="-1"></a> </span> 215 - <span id="cb12-9"><a href="#cb12-9" aria-hidden="true" tabindex="-1"></a> <span class="va">outputs</span> <span class="op">=</span> <span class="op">{</span> <span class="va">self</span><span class="op">,</span> <span class="va">official-templates</span><span class="op">,</span> <span class="va">other-templates</span> <span class="op">...</span> <span class="op">}</span>: <span class="op">{</span></span> 216 - <span id="cb12-10"><a href="#cb12-10" aria-hidden="true" tabindex="-1"></a> </span> 217 - <span id="cb12-11"><a href="#cb12-11" aria-hidden="true" tabindex="-1"></a> <span class="va">templates</span> <span class="op">=</span> <span class="op">{</span></span> 218 - <span id="cb12-12"><a href="#cb12-12" aria-hidden="true" tabindex="-1"></a> <span class="va">latex-report</span> <span class="op">=</span> <span class="op">{</span></span> 219 - <span id="cb12-13"><a href="#cb12-13" aria-hidden="true" tabindex="-1"></a> <span class="va">path</span> <span class="op">=</span> <span class="ss">./latex-report-template</span><span class="op">;</span></span> 220 - <span id="cb12-14"><a href="#cb12-14" aria-hidden="true" tabindex="-1"></a> <span class="va">description</span> <span class="op">=</span> <span class="st">&quot;A latex whitepaper project&quot;</span><span class="op">;</span></span> 221 - <span id="cb12-15"><a href="#cb12-15" aria-hidden="true" tabindex="-1"></a> <span class="op">};</span></span> 222 - <span id="cb12-16"><a href="#cb12-16" aria-hidden="true" tabindex="-1"></a> <span class="va">rust-hello</span> <span class="op">=</span> <span class="op">{</span></span> 223 - <span id="cb12-17"><a href="#cb12-17" aria-hidden="true" tabindex="-1"></a> <span class="va">path</span> <span class="op">=</span> <span class="ss">./rust-hello-template</span><span class="op">;</span></span> 224 - <span id="cb12-18"><a href="#cb12-18" aria-hidden="true" tabindex="-1"></a> <span class="va">description</span> <span class="op">=</span> <span class="st">&quot;Simple Hello World in Rust, with overloaded Rust toolchain&quot;</span><span class="op">;</span></span> 225 - <span id="cb12-19"><a href="#cb12-19" aria-hidden="true" tabindex="-1"></a> <span class="op">};</span></span> 226 - <span id="cb12-20"><a href="#cb12-20" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span> 227 - <span id="cb12-21"><a href="#cb12-21" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="op">//</span> official<span class="op">-</span>templates.templates</span> 228 - <span id="cb12-22"><a href="#cb12-22" aria-hidden="true" tabindex="-1"></a><span class="op">+</span> <span class="op">//</span> other<span class="op">-</span>templates.templates<span class="op">;</span></span> 229 - <span id="cb12-23"><a href="#cb12-23" aria-hidden="true" tabindex="-1"></a> </span> 230 - <span id="cb12-24"><a href="#cb12-24" aria-hidden="true" tabindex="-1"></a> <span class="op">};</span></span> 231 - <span id="cb12-25"><a href="#cb12-25" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span></code></pre></div> 232 - <p>Running <code>nix flake show biscuits</code> will now list templates 233 - from the <code>biscuits</code> registry as well as the ones from 234 - <code>NixOS/templates</code>. Ensure that the names don’t collide 235 - though.</p> 236 - 237 - </div> 238 - 239 - <div class="intro"> 240 - Hi. 241 - <div class="hot-links"> 242 - <a href="/index.xml" class="feed-button">Subscribe</a> 243 - </div> 244 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 245 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 246 - <p>Reach out at oppili@libera.chat.</p> 247 - </div> 248 - 249 - <a href="/" class="post-end-link">Home</a> 250 - <span>/</span> 251 - <a href="/posts" class="post-end-link">Posts</a> 252 - <span>/</span> 253 - <a class="post-end-link">Novice Nix: Flake Templates</a> 254 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/novice_nix:_flake_templates.md 255 - ">View Raw</a> 256 - </div> 257 - </div> 258 - </body> 259 - </html>
-100
docs/posts/onivim_sucks/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Onivim Sucks"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/onivim_sucks"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Onivim Sucks · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Onivim Sucks</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/onivim_sucks.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 02/08 — 2019 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 11.19 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 1.3 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Onivim Sucks 43 - </h1> 44 - <div class="post-text"> 45 - <p><a href="https://v2.onivim.io">Onivim</a> is a ‘modern modal editor’, 46 - combining fancy interface and language features with vim-style modal 47 - editing. What’s wrong you ask?</p> 48 - <p>Apart from <a href="https://github.com/onivim/oni2/issues/550">buggy 49 - syntax highlighting</a>, <a 50 - href="https://github.com/onivim/oni2/issues/519">broken scrolling</a> 51 - and <a 52 - href="https://github.com/onivim/oni2/issues?q=is%3Aissue+label%3A%22daily+editor+blocker%22+is%3Aopen">others</a>, 53 - Onivim is <strong>proprietary</strong> software. It is licensed under a 54 - commercial <a 55 - href="https://github.com/onivim/oni1/blob/master/Outrun-Labs-EULA-v1.1.md">end 56 - user agreement license</a>, which prohibits redistribution in both 57 - object code and source code formats.</p> 58 - <p>Onivim’s core editor logic (bits that belong to vim), have been 59 - separated from the interface, into <a 60 - href="https://github.com/onivim/libvim">libvim</a>. libvim is licensed 61 - under MIT, which means, this ‘extension’ of vim is perfectly in 62 - adherence to <a 63 - href="http://vimdoc.sourceforge.net/htmldoc/uganda.html#license">vim’s 64 - license text</a>! Outrun Labs are exploiting this loophole (distributing 65 - vim as a library) to commercialize Onivim.</p> 66 - <p>Onivim’s source code is available on <a 67 - href="https://github.com/onivim/oni2">GitHub</a>. They do mention that 68 - the source code trickles down to the <a 69 - href="https://github.com/onivim/oni2-mit">oni2-mit</a> repository, which 70 - (not yet) contains MIT-licensed code, <strong>18 months</strong> after 71 - each commit to the original repository.</p> 72 - <p>Want to contribute to Onivim? Don’t. They make a profit out of your 73 - contributions. Currently, Onivim is priced at $19.99, ‘pre-alpha’ 74 - pricing which is 80% off the final price! If you are on the lookout for 75 - an editor, I would suggest using <a href="https://vim.org">Vim</a>, 76 - charity ware that actually works, and costs $100 lesser.</p> 77 - 78 - </div> 79 - 80 - <div class="intro"> 81 - Hi. 82 - <div class="hot-links"> 83 - <a href="/index.xml" class="feed-button">Subscribe</a> 84 - </div> 85 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 86 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 87 - <p>Reach out at oppili@libera.chat.</p> 88 - </div> 89 - 90 - <a href="/" class="post-end-link">Home</a> 91 - <span>/</span> 92 - <a href="/posts" class="post-end-link">Posts</a> 93 - <span>/</span> 94 - <a class="post-end-link">Onivim Sucks</a> 95 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/onivim_sucks.md 96 - ">View Raw</a> 97 - </div> 98 - </div> 99 - </body> 100 - </html>
-168
docs/posts/pixel_art_in_GIMP/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Pixel Art In GIMP"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/pixel_art_in_GIMP"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Pixel Art In GIMP · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Pixel Art In GIMP</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/pixel_art_in_GIMP.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 08/04 — 2020 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 55.54 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 5.0 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Pixel Art In GIMP 43 - </h1> 44 - <div class="post-text"> 45 - <p>I’ve always been an admirer of pixel art, because of it’s simplicity 46 - and it’s resemblance to bitmap font design. Recently, I decided to take 47 - the dive and make some art of my own.</p> 48 - <p>I used GIMP because I am fairly familiar with it. Aseprite seems to 49 - be the editor of choice for animated pixel art though.</p> 50 - <h3 id="setting-up-the-canvas">Setting up the canvas</h3> 51 - <p>Picking a canvas size is daunting. Too small, and you won’t be able 52 - to fit in enough detail to make a legible piece. Too big and you’ve got 53 - too many pixels to work with!</p> 54 - <p>I would suggest starting out with anywhere between 100x100 and 55 - 200x200. <a href="https://u.peppe.rs/u9.png">Here’s</a> a sample 56 - configuration.</p> 57 - <p>Sometimes I use a 10x10 grid, <code>View &gt; Show Grid</code> and 58 - <code>Edit &gt; Preferences &gt; Default Grid &gt; Spacing</code>, but 59 - that can get jarring, so I throw down a couple of guides, drag right or 60 - down from the left or top gutters for vertical and horizontal guides 61 - respectively.</p> 62 - <h3 id="choosing-a-brush">Choosing a Brush</h3> 63 - <p>The most important part of our setup is the brush. Use the Pencil 64 - Tool (<code>n</code> on the keyboard) for hard edge drawings. Here’s a 65 - small comparison if you don’t know the difference between a hard edge 66 - and a soft edge:</p> 67 - <figure> 68 - <img src="https://u.peppe.rs/kz.png" alt="Hard edge vs Soft Edge" /> 69 - <figcaption aria-hidden="true">Hard edge vs Soft Edge</figcaption> 70 - </figure> 71 - <p>I turn the size down all the way to 1 (<code>[</code> on the 72 - keyboard). Set <code>Dynamics</code> off. <a 73 - href="https://u.peppe.rs/Fs.png">Here’s</a> a sample brush 74 - configuration.</p> 75 - <h3 id="laying-down-the-pixels">Laying down the pixels!</h3> 76 - <p>With the boring stuff out of the way, we can start with our piece. I 77 - usually follow a three step process:</p> 78 - <ul> 79 - <li>draw a rough outline</li> 80 - <li>fill in the shadows</li> 81 - <li>add highlights</li> 82 - </ul> 83 - <p>But this process is better explained with an example: an onigiri. Let 84 - us start off with a 100x100 canvas.</p> 85 - <h4 id="drawing-the-outline">Drawing the outline</h4> 86 - <p>For the most part, our figure will be symmetric. If you are on GIMP 87 - 2.10+, you can take advantage of the Symmetry Painting feature. Go ahead 88 - and enable vertical symmetry, 89 - <code>Window &gt; Dockable Dialogs &gt; Symmetry Painting</code> and 90 - <code>Symmetry Painting &gt; Symmetry &gt; Mirror &gt; Vertical</code>.</p> 91 - <p>If you are running an older version of GIMP, draw in the left side, 92 - duplicate the layer, flip it horizontally, and merge it with the 93 - original.</p> 94 - <p>Your outline might look something like this:</p> 95 - <p><img src="https://u.peppe.rs/mn.png" /></p> 96 - <p>Go ahead and fill it in with the fill tool (<code>Shift + b</code> on 97 - the keyboard), add in some seaweed as well, preferably on a different 98 - layer. You can toggle symmetry on and off to save yourself some 99 - time.</p> 100 - <p><img src="https://u.peppe.rs/xu.png" /></p> 101 - <h4 id="shadows">Shadows</h4> 102 - <p>For now, let us focus on the shadows on the object itself, we’ll come 103 - back to the shadows cast by the object on the surface later.</p> 104 - <p>Shadows on any surface always follow the shape of the surface. A 105 - spherical onigiri would have a circular shadow:</p> 106 - <p><img src="https://u.peppe.rs/FU.png" /></p> 107 - <p>A couple of noticeable changes:</p> 108 - <p><strong>Layers</strong>: The layer containing the seaweed has been 109 - hidden.<br /> 110 - <strong>Color</strong>: The color of the shadow is just a slightly 111 - lighter version of the original object (reduce the Value on the HSV 112 - scale).<br /> 113 - <strong>Area</strong>: The shadow does not go all the way (notice the 114 - bottom edges).</p> 115 - <p>The shadow does not go all the way because we will be filling in that 116 - area with another, darker shadow! An image might explain better:</p> 117 - <p><img src="https://u.peppe.rs/Br.png" /></p> 118 - <p>To emulate soft lights, reduce the value by 2 to 3 points every 119 - iteration. Notice how area <code>1</code> is much larger than area 120 - <code>4</code>. This is because an onigiri resembles a bottom heavy 121 - oblate spheroid, a sphere that is slightly fatter around the lower 122 - bottom, and areas <code>1</code> and <code>2</code> catch more light 123 - than areas <code>3</code> and <code>4</code>.</p> 124 - <p>Do the same with the seaweed. The seaweed, being a smaller, flatter 125 - object, doesn’t cast much of a shadow, so stop with 1 or 2 iterations of 126 - the gradient:</p> 127 - <p><img src="https://u.peppe.rs/T3.png" /></p> 128 - <p>We’re getting there!</p> 129 - <h4 id="highlights">Highlights</h4> 130 - <p>This step handles the details on the strongly illuminated portions of 131 - the object. Seaweed is a bit glossy, lighten the edges to make it seem 132 - shiny. The rice is not as shiny, but it does form an uneven surface. Add 133 - in some shadows to promote the idea of rice grains. Here is the finished 134 - result:</p> 135 - <p><img src="https://u.peppe.rs/VE.png" /></p> 136 - <h3 id="finishing-touches">Finishing Touches</h3> 137 - <p>Some color correction and <code>a e s t h e t i c</code> Japanese 138 - text later, our piece is complete!</p> 139 - <p><img src="https://u.peppe.rs/cn.png" /></p> 140 - <p>Hold on, why is it so tiny? Well, that’s because our canvas was 141 - 100x100, head over to <code>Image &gt; Scale Image</code>, set 142 - <code>Quality &gt; Interpolation</code> to <code>None</code> and scale 143 - it up to 700x700, et voilà!</p> 144 - <p><img src="https://u.peppe.rs/CH.png" /></p> 145 - 146 - </div> 147 - 148 - <div class="intro"> 149 - Hi. 150 - <div class="hot-links"> 151 - <a href="/index.xml" class="feed-button">Subscribe</a> 152 - </div> 153 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 154 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 155 - <p>Reach out at oppili@libera.chat.</p> 156 - </div> 157 - 158 - <a href="/" class="post-end-link">Home</a> 159 - <span>/</span> 160 - <a href="/posts" class="post-end-link">Posts</a> 161 - <span>/</span> 162 - <a class="post-end-link">Pixel Art In GIMP</a> 163 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/pixel_art_in_GIMP.md 164 - ">View Raw</a> 165 - </div> 166 - </div> 167 - </body> 168 - </html>
-303
docs/posts/plain_text_journaling/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Plain Text Journaling"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/plain_text_journaling"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Plain Text Journaling · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Plain Text Journaling</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/plain_text_journaling.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 18/06 — 2023 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 138.66 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 8.9 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Plain Text Journaling 43 - </h1> 44 - <div class="post-text"> 45 - <p>I cobbled together a journaling system with {neo,}vim, coreutils and 46 - <a href="http://www.fresse.org/dateutils">dateutils</a>. This system is 47 - loosely based on <a href="https://www.rydercarroll.com/">Ryder 48 - Caroll’s</a> Bullet Journal method.</p> 49 - <p><a href="https://u.peppe.rs/SpF.png"><img 50 - src="https://u.peppe.rs/SpF.png" /></a></p> 51 - <h3 id="the-format">The format</h3> 52 - <p>The journal for a given year is a directory:</p> 53 - <div class="sourceCode" id="cb1"><pre 54 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> ls journal/</span> 55 - <span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">2022/</span> 2023/</span></code></pre></div> 56 - <p>In each directory are 12 files, one for each month of the year, 57 - numbered like so:</p> 58 - <div class="sourceCode" id="cb2"><pre 59 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> ls journal/2023/</span> 60 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="ex">01</span> 02 03 04 05 06 07 08 09 10 11 12</span></code></pre></div> 61 - <p>We can now begin writing stuff down:</p> 62 - <div class="sourceCode" id="cb3"><pre 63 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> vim journal/2023/1</span></code></pre></div> 64 - <p>Every month must start with a calendar of course, fill that in 65 - with:</p> 66 - <pre class="vim"><code>:read !cal -m</code></pre> 67 - <p>Your entry for January might look like this:</p> 68 - <div class="sourceCode" id="cb5"><pre 69 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> cat journal/2023/01</span> 70 - <span id="cb5-2"><a href="#cb5-2" aria-hidden="true" tabindex="-1"></a> <span class="ex">January</span> 2023</span> 71 - <span id="cb5-3"><a href="#cb5-3" aria-hidden="true" tabindex="-1"></a><span class="ex">Mo</span> Tu We Th Fr Sa Su</span> 72 - <span id="cb5-4"><a href="#cb5-4" aria-hidden="true" tabindex="-1"></a> <span class="ex">1</span></span> 73 - <span id="cb5-5"><a href="#cb5-5" aria-hidden="true" tabindex="-1"></a> <span class="ex">2</span> 3 4 5 6 7 8</span> 74 - <span id="cb5-6"><a href="#cb5-6" aria-hidden="true" tabindex="-1"></a> <span class="ex">9</span> 10 11 12 13 14 15</span> 75 - <span id="cb5-7"><a href="#cb5-7" aria-hidden="true" tabindex="-1"></a><span class="ex">16</span> 17 18 19 20 21 22</span> 76 - <span id="cb5-8"><a href="#cb5-8" aria-hidden="true" tabindex="-1"></a><span class="ex">23</span> 24 25 26 27 28 29</span> 77 - <span id="cb5-9"><a href="#cb5-9" aria-hidden="true" tabindex="-1"></a><span class="ex">30</span> 31</span></code></pre></div> 78 - <p>I prefer planning week by week, as opposed to creating a task-list 79 - every day, here’s what I have for the first couple of weeks:</p> 80 - <pre><code> January 2023 81 - Mo Tu We Th Fr Sa Su 82 - 1 83 - 2 3 4 5 6 7 8 84 - 9 10 11 12 13 14 15 85 - 16 17 18 19 20 21 22 86 - 23 24 25 26 27 28 29 87 - 30 31 88 - 89 - 90 - week 1 91 - 92 - done apply leaves 93 - done dload boarding pass 94 - moved reply to dan 95 - 96 - 97 - week 2 98 - 99 - todo reply to dan 100 - todo pack bags 101 - done travel insurance 102 - todo weigh luggage</code></pre> 103 - <p>I start the week by writing a header and each item that week is 104 - placed on its own line. The items are prefixed with a <code>todo</code> 105 - or a <code>done</code> signifier.</p> 106 - <h3 id="form-over-function">Form over function</h3> 107 - <p>Right off the bat, the signifiers look very noisy, Even more so once 108 - we start introducing variety (I use “event”, “note” and “moved”):</p> 109 - <pre><code>week 1 110 - 111 - todo apply leaves 112 - done dload boarding pass 113 - todo reply to dan 114 - event fr trip 115 - note weight 68.6</code></pre> 116 - <p>We can clean this up with “abbreviations” 117 - (<code>:h abbreviations</code>):</p> 118 - <pre class="vim"><code>:iabbrev todo · 119 - :iabbrev done ×</code></pre> 120 - <p>Now, typing this:</p> 121 - <pre><code>todo apply leaves</code></pre> 122 - <p>Automatically inserts:</p> 123 - <pre><code>· apply leaves</code></pre> 124 - <p>You can use <code>x</code> and <code>o</code> as well, but 125 - <code>×</code> (U+00D7, MULTIPLICATION SIGN) and <code>·</code> (U+00B7, 126 - MIDDLE DOT) are more … <em>gourmet</em>.</p> 127 - <p>The other signifiers I use are:</p> 128 - <ul> 129 - <li><code>-</code> for note</li> 130 - <li><code>o</code> for event</li> 131 - <li><code>&gt;</code> for moved.</li> 132 - </ul> 133 - <p>Nit #2 is the lack of order. We can employ vim to introduce grouping 134 - and sorting. Select the list of entries for this week:</p> 135 - <pre class="vim"><code>vip &quot; line-wise select inner paragraph 136 - :&#39;&lt;,&#39;&gt;sort &quot; the markers &#39;&lt; and &#39;&gt; are automatically inserted, 137 - &quot; they mark the start and end of the selection</code></pre> 138 - <p>We end up with:</p> 139 - <pre><code>week 1 140 - 141 - · apply leaves 142 - · reply to dan 143 - × dload boarding pass</code></pre> 144 - <p>The lines are grouped by their signifiers, segregating todo items 145 - from completed items. Luckily, MIDDLE DOT is lesser than MULTIPLICATION 146 - SIGN, so todo items are placed at the top. The same goes for 147 - <code>o</code> and <code>x</code> symbols, either set of signifiers will 148 - result in the same sorting order.</p> 149 - <p>We can shorten this select-paragraph-invoke-sort dance by setting the 150 - <code>formatprg</code> variable:</p> 151 - <pre class="vim"><code>:set formatprg=sort\ -V</code></pre> 152 - <p>Now, hitting <code>gqip</code> should automatically group and sort 153 - the items for the week under the cursor, moving todo items to the top. 154 - Finding signifier glyphs that suit your sorting preference is a fun 155 - exercise.</p> 156 - <h3 id="syntax-highlighting">Syntax highlighting</h3> 157 - <p>Adding color to items introduces another layer of visual distinction. 158 - In truth, I like to deck it out just because.</p> 159 - <p>First, create a few syntax groups:</p> 160 - <pre class="vim"><code>:syntax match JournalAll /.*/ &quot; captures the entire buffer 161 - :syntax match JournalDone /^×.*/ &quot; lines containing &#39;done&#39; items: × 162 - :syntax match JournalTodo /^·.*/ &quot; lines containing &#39;todo&#39; items: · 163 - :syntax match JournalEvent /^o.*/ &quot; lines containing &#39;event&#39; items: o 164 - :syntax match JournalNote /^- .*/ &quot; lines containing &#39;note&#39; items: - 165 - :syntax match JournalMoved /^&gt;.*/ &quot; lines containing &#39;moved&#39; items: &gt;</code></pre> 166 - <p>Add highlights to each group:</p> 167 - <pre class="vim"><code>:highlight JournalAll ctermfg=12 &quot; bright black 168 - :highlight JournalDone ctermfg=12 &quot; bright black 169 - :highlight JournalEvent ctermfg=6 &quot; cyan 170 - :highlight JournalMoved ctermfg=5 &quot; magenta 171 - :highlight JournalNote ctermfg=3 &quot; yellow</code></pre> 172 - <p>In my terminal, this is rendered like so:</p> 173 - <p><a href="https://u.peppe.rs/Du6.png"><img 174 - src="https://u.peppe.rs/Du6.png" /></a></p> 175 - <h3 id="habit-tracking">Habit tracking</h3> 176 - <p>While this is not a part of my journaling system anymore, a few 177 - headers and an awk script is all it takes to track habits. My weekly 178 - entries would include a couple of habit headers like so:</p> 179 - <pre><code>week 1 -------------- 180 - 181 - × wake up on time 182 - × water the plants 183 - 184 - spend 7.5 7 10 185 - --------------------- 186 - 187 - 188 - week 2 -------------- 189 - 190 - · make the bed 191 - · go to bed 192 - 193 - spend 30 2.75 6 194 - ---------------------</code></pre> 195 - <p>Here, under the <code>spend</code> header in week 1, are a list of 196 - expenditures accumulated over the week. The monthly spend is calculated 197 - with this awk script:</p> 198 - <div class="sourceCode" id="cb17"><pre 199 - class="sourceCode awk"><code class="sourceCode awk"><span id="cb17-1"><a href="#cb17-1" aria-hidden="true" tabindex="-1"></a><span class="cf">BEGIN</span> <span class="op">{</span>spend<span class="op">=</span><span class="dv">0</span><span class="op">;}</span></span> 200 - <span id="cb17-2"><a href="#cb17-2" aria-hidden="true" tabindex="-1"></a><span class="ot">/</span><span class="ss">spend</span><span class="ot">/</span> <span class="op">{</span><span class="cf">for</span><span class="op">(</span>i<span class="op">=</span><span class="dv">1</span><span class="op">;</span>i<span class="op">&lt;=</span><span class="dt">$NF</span><span class="op">;</span>i<span class="op">++)</span> spend<span class="op">+=</span><span class="dt">$i</span><span class="op">;}</span></span> 201 - <span id="cb17-3"><a href="#cb17-3" aria-hidden="true" tabindex="-1"></a><span class="cf">END</span> <span class="op">{</span> <span class="kw">printf</span> spend <span class="st">&quot;eur&quot;</span><span class="op">}</span></span></code></pre></div> 202 - <p>And invoked like so:</p> 203 - <pre><code>λ awk -f spend.awk journal/2023/01 204 - 63.25eur</code></pre> 205 - <h3 id="reflection">Reflection</h3> 206 - <p>Journaling is not just about planning what is to come, but also 207 - reflecting on what has passed. It would make sense to simultaneously 208 - look at the past few weeks’ entries while making your current one. To 209 - open multiple months of entries at the same time:</p> 210 - <pre><code>λ vim -O journal/2023/0{1,2,3}</code></pre> 211 - <p>Opens 3 months, side-by-side, in vertical splits:</p> 212 - <pre><code>JANUARY ------------ │ FEBRUARY ----------- │ MARCH -------------- 213 - │ │ 214 - Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su │ Mo Tu We Th Fr Sa Su 215 - 1 │ 1 2 3 4 5 │ 1 2 3 4 5 216 - 2 3 4 5 6 7 8 │ 6 7 8 9 10 11 12 │ 6 7 8 9 10 11 12 217 - 9 10 11 12 13 14 15 │ 13 14 15 16 17 18 19 │ 13 14 15 16 17 18 19 218 - 16 17 18 19 20 21 22 │ 20 21 22 23 24 25 26 │ 20 21 22 23 24 25 26 219 - 23 24 25 26 27 28 29 │ 27 28 │ 27 28 29 30 31 220 - 30 31 │ │ 221 - │ │ 222 - │ │ 223 - WEEK 1 ------------- │ WEEK 1 ------------- │ WEEK 1 ------------- 224 - │ │ 225 - &gt; latex setup │ &gt; forex │ - weight: 64 226 - × make the bed │ × clean shoes │ &gt; close sg-pr 227 - × 03: dentist │ × buy clothes │ × facewash 228 - × integrate tsg │ × draw │ × groceries 229 - │ │ 230 - │ │ 231 - WEEK 2 ------------- │ WEEK 2 ------------- │ WEEK 2 ------------- 232 - │ │ 233 - × latex setup │ - viral fever │ &gt; close sg-pr 234 - × send invoice │ × forex │ × plan meet 235 - × stack-graph pr │ × activate sim │ × sg storage 236 - │ × bitlbee │</code></pre> 237 - <h3 id="reducing-friction">Reducing friction</h3> 238 - <p>Journaling already requires a solid amount of discipline and 239 - consistency. The added friction of typing 240 - <code>vim journal/$CURRENT_YEAR/$CURRENT_MONTH</code> each time is doing 241 - no favors.</p> 242 - <p>To open the current month based on system time:</p> 243 - <div class="sourceCode" id="cb21"><pre 244 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb21-1"><a href="#cb21-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> vim <span class="va">$(</span><span class="fu">date</span> +<span class="st">&quot;%Y/%m&quot;</span><span class="va">)</span></span></code></pre></div> 245 - <p>To open all the months within a 2 month window of today, is a little 246 - trickier. The command we wish to generate is (if today is 2023/12):</p> 247 - <div class="sourceCode" id="cb22"><pre 248 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb22-1"><a href="#cb22-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> vim <span class="at">-O</span> 2023/10 2023/11 2023/12 2024/01 2024/02</span></code></pre></div> 249 - <p>And that is where <code>dateseq</code> from <a 250 - href="http://www.fresse.org/dateutils">dateutils</a> comes in handy, for 251 - example:</p> 252 - <div class="sourceCode" id="cb23"><pre 253 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb23-1"><a href="#cb23-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> dateseq 2012-02-01 2012-03-01</span> 254 - <span id="cb23-2"><a href="#cb23-2" aria-hidden="true" tabindex="-1"></a><span class="ex">2012-02-01</span></span> 255 - <span id="cb23-3"><a href="#cb23-3" aria-hidden="true" tabindex="-1"></a><span class="ex">2012-02-02</span></span> 256 - <span id="cb23-4"><a href="#cb23-4" aria-hidden="true" tabindex="-1"></a><span class="ex">2012-02-03</span></span> 257 - <span id="cb23-5"><a href="#cb23-5" aria-hidden="true" tabindex="-1"></a><span class="ex">...</span></span> 258 - <span id="cb23-6"><a href="#cb23-6" aria-hidden="true" tabindex="-1"></a><span class="ex">2012-02-28</span></span> 259 - <span id="cb23-7"><a href="#cb23-7" aria-hidden="true" tabindex="-1"></a><span class="ex">2012-02-29</span></span> 260 - <span id="cb23-8"><a href="#cb23-8" aria-hidden="true" tabindex="-1"></a><span class="ex">2012-03-01</span></span></code></pre></div> 261 - <p>This script opens all months within a 2 month window of today:</p> 262 - <div class="sourceCode" id="cb24"><pre 263 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb24-1"><a href="#cb24-1" aria-hidden="true" tabindex="-1"></a><span class="ex">λ</span> vim <span class="at">-O</span> <span class="va">$(</span></span> 264 - <span id="cb24-2"><a href="#cb24-2" aria-hidden="true" tabindex="-1"></a> <span class="ex">dateseq</span> <span class="dt">\</span></span> 265 - <span id="cb24-3"><a href="#cb24-3" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;</span><span class="va">$(</span><span class="fu">date</span> <span class="at">--date</span> <span class="st">&quot;2 months ago&quot;</span> +%Y/%m<span class="va">)</span><span class="st">&quot;</span> <span class="dt">\</span></span> 266 - <span id="cb24-4"><a href="#cb24-4" aria-hidden="true" tabindex="-1"></a> <span class="st">&quot;</span><span class="va">$(</span><span class="fu">date</span> <span class="at">--date</span> <span class="st">&quot;2 months&quot;</span> +%Y/%m<span class="va">)</span><span class="st">&quot;</span> <span class="dt">\</span></span> 267 - <span id="cb24-5"><a href="#cb24-5" aria-hidden="true" tabindex="-1"></a> <span class="at">-i</span> %Y/%m <span class="dt">\</span></span> 268 - <span id="cb24-6"><a href="#cb24-6" aria-hidden="true" tabindex="-1"></a> <span class="at">-f</span> %Y/%m</span> 269 - <span id="cb24-7"><a href="#cb24-7" aria-hidden="true" tabindex="-1"></a><span class="va">)</span></span></code></pre></div> 270 - <h3 id="fin">Fin</h3> 271 - <p>You can find a sample vimrc file here: <a 272 - href="https://git.peppe.rs/cli/journal/tree">cli/journal</a>, along with 273 - a nix flake file to kick things off.</p> 274 - <p>Plain text journaling can be just as much fun as a pen and paper. 275 - Throw in some ASCII art for each month, use swankier signifiers, or 276 - louder syntax highlighting. Don’t expect forgiveness from org-mode users 277 - though.</p> 278 - <p><a href="https://u.peppe.rs/ZCK.png"><img 279 - src="https://u.peppe.rs/ZCK.png" /></a></p> 280 - 281 - </div> 282 - 283 - <div class="intro"> 284 - Hi. 285 - <div class="hot-links"> 286 - <a href="/index.xml" class="feed-button">Subscribe</a> 287 - </div> 288 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 289 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 290 - <p>Reach out at oppili@libera.chat.</p> 291 - </div> 292 - 293 - <a href="/" class="post-end-link">Home</a> 294 - <span>/</span> 295 - <a href="/posts" class="post-end-link">Posts</a> 296 - <span>/</span> 297 - <a class="post-end-link">Plain Text Journaling</a> 298 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/plain_text_journaling.md 299 - ">View Raw</a> 300 - </div> 301 - </div> 302 - </body> 303 - </html>
-226
docs/posts/programming_on_34_keys/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Programming On 34 Keys"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/programming_on_34_keys"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Programming On 34 Keys · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Programming On 34 Keys</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/programming_on_34_keys.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 28/08 — 2022 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 63.54 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 6.2 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Programming On 34 Keys 43 - </h1> 44 - <div class="post-text"> 45 - <p>Minimizing your keyboard layout is a slippery slope. A few months 46 - ago, I built the <a 47 - href="https://github.com/icyphox/ferricy">Ferricy</a>, a 48 - 34-key-split-ortho-ergo keyboard. The Ferricy is a fork of the <a 49 - href="https://github.com/davidphilipbarr/Sweep/tree/main/Sweep%20Bling%20MX">Ferris 50 - Sweep MX Bling</a>.</p> 51 - <figure> 52 - <img src="https://u.peppe.rs/otz.jpg" 53 - alt="The Ferricy, designed by icyphox" /> 54 - <figcaption aria-hidden="true">The Ferricy, designed by <a 55 - href="https://icyphox.sh">icyphox</a></figcaption> 56 - </figure> 57 - <p>My daily use consists of a bit of prose and a lot of program, my 58 - layout has evolved accordingly.</p> 59 - <h1 id="base-layer">Base Layer</h1> 60 - <figure> 61 - <img src="https://u.peppe.rs/base.png" alt="Colemak with no mods" /> 62 - <figcaption aria-hidden="true">Colemak with no mods</figcaption> 63 - </figure> 64 - <p>The base layer contains alphabets, four symbols and four whitespace 65 - keys:</p> 66 - <ul> 67 - <li>Alphas: Stock Colemak, with no modifications whatsoever</li> 68 - <li>Symbols: <code>. , / ;</code></li> 69 - <li>Whitespace: tab, space, enter, backspace (from left to right)</li> 70 - </ul> 71 - <h1 id="layers">Layers</h1> 72 - <p>Keyboard input is complex and it is impossible to skirt around it. 73 - You can either use a keyboard with enough keys to supply all possible 74 - inputs (a mechanical burden), or you can use firmware to supply all 75 - possible inputs (a cognitive burden). Layers are a cognitive burden.</p> 76 - <p>I use 3 layers, heavily inspired by <a 77 - href="https://github.com/manna-harbour/miryoku">Miryoku</a>, but tuned 78 - for programming. Excluding the base Colemak layer:</p> 79 - <ul> 80 - <li><code>NAV</code>: activated on holding <code>space</code> (left 81 - thumb)</li> 82 - <li><code>NUM</code>: activated on holding <code>tab</code> (left 83 - thumb)</li> 84 - <li><code>SYM</code>: activated on holding <code>enter</code> (right 85 - thumb)</li> 86 - </ul> 87 - <h2 id="the-nav-layer">The <code>NAV</code> Layer</h2> 88 - <p>As the name suggests, this layer is focused on navigation. Arrow keys 89 - and the likes.</p> 90 - <figure> 91 - <img src="https://u.peppe.rs/nav.png" alt="NAV, on holding space" /> 92 - <figcaption aria-hidden="true"><code>NAV</code>, on holding 93 - <code>space</code></figcaption> 94 - </figure> 95 - <p>Using Vim and Colemak means you lose out on HJKL navigation. However, 96 - on activating the <code>NAV</code> layer, the right home-row is 97 - converted into arrow keys. In essence, by holding space, I can navigate 98 - Vim with the home-row, or Firefox, or my PDF reader. I no longer need to 99 - look for software that allows Vim navigation keys, because it is baked 100 - into the firmware!</p> 101 - <p>My Vim motions are not limited to HJKL. In fact, my Vim motions are 102 - rarely HJKL. I tend to use <code>}</code> (next paragraph) and 103 - <code>)</code> (next sentence) more often. As a result, these have found 104 - their way into my <code>NAV</code> layer, over the likes of 105 - <code>PgDown</code> and <code>End</code>. Having brackets at my index 106 - and middle fingers is nice for programming too.</p> 107 - <h2 id="the-sym-layer">The <code>SYM</code> Layer</h2> 108 - <figure> 109 - <img src="https://u.peppe.rs/sym.png" alt="SYM, on holding enter" /> 110 - <figcaption aria-hidden="true"><code>SYM</code>, on holding 111 - <code>enter</code></figcaption> 112 - </figure> 113 - <p>This layer contains all the symbols that you would find by hitting 114 - <code>Shift</code> and a key on the number row. Probably noteworthy to 115 - Vim users: the symbols are arranged in the form of a mirrored numpad for 116 - exactly one reason: to move <code>$</code> to the left of 117 - <code>^</code>. It has always annoyed me that <code>$</code> moves the 118 - cursor to the end of the line and <code>^</code> moves it to the 119 - beginning, but their position on a typical number row are reversed, 4 120 - comes before 6.</p> 121 - <h2 id="the-num-layer">The <code>NUM</code> layer</h2> 122 - <figure> 123 - <img src="https://u.peppe.rs/num.png" alt="NUM, on holding tab" /> 124 - <figcaption aria-hidden="true"><code>NUM</code>, on holding 125 - <code>tab</code></figcaption> 126 - </figure> 127 - <p>Another deviation from Miryoku, the numpad just feels <em>right</em> 128 - on my <em>right</em> hand.</p> 129 - <h1 id="zmk-combos">ZMK Combos</h1> 130 - <p>If you have been paying close attention, you might have noticed that 131 - <code>escape</code> didn’t make it to any layer. <code>escape</code> is 132 - too crucial to put on a non-base layer, but at the same time, not as 133 - important to deserve a place on the base layer. That is where ZMK’s 134 - combos come in. Combos let you tap any number of keys, and combine them 135 - to form a single key. I have combos set up for underscore, minus, escape 136 - and caps-word (more on caps-word later):</p> 137 - <figure> 138 - <img src="https://u.peppe.rs/combos.png" 139 - alt="Combos are almost piano-like" /> 140 - <figcaption aria-hidden="true">Combos are almost piano-like</figcaption> 141 - </figure> 142 - <h1 id="home-row-mods">Home-row Mods</h1> 143 - <p>Inherited from Miryoku, I have home-row mods for activating 144 - <code>Super</code>, <code>Alt</code>, <code>Shift</code>, 145 - <code>Ctrl</code> and <code>Hyper</code> 146 - (<code>Ctrl + Shift + Alt + Super</code>). The idea is to send 147 - <code>T</code> on tap and <code>Ctrl</code> on hold. Home-row mods are 148 - fairly popular, so I’ll not go into the details.</p> 149 - <figure> 150 - <img src="https://u.peppe.rs/homerow.png" 151 - alt="Super, Alt, Shift, Ctrl, Hyper; on the left half, and mirrored on the right half" /> 152 - <figcaption aria-hidden="true">Super, Alt, Shift, Ctrl, Hyper; on the 153 - left half, and mirrored on the right half</figcaption> 154 - </figure> 155 - <p><code>Hyper</code> bridges the gap between firmware and software. You 156 - can never configure key combination that, opens Firefox, for example, 157 - through firmware alone. However, with the <code>Hyper</code> key, and 158 - some <code>sxhkd</code> magic, you can emulate that. Pressing 159 - <code>Hyper + F</code> on a keyboard is just two keys, but the key codes 160 - sent are <code>Ctrl + Shift + Alt + Super + F</code>. That key 161 - combination is not intercepted by any application as a shortcut, except 162 - for the following <code>sxhkd</code> stanza:</p> 163 - <div class="sourceCode" id="cb1"><pre 164 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">super</span> + alt + shift + ctrl + f</span> 165 - <span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> <span class="ex">xdotool</span> search <span class="st">&quot;Mozilla Firefox&quot;</span> windowactivate</span></code></pre></div> 166 - <p>Alternatively, you can intercept unused <code>F</code> keys: 167 - <code>F13</code> through <code>F24</code>.</p> 168 - <p>Home-row mods are mirrored on each half because it would be 169 - impossible to hit <code>Ctrl + T</code> if not; they lie on the same 170 - key.</p> 171 - <h1 id="caps-word">Caps-word</h1> 172 - <p>Caps-word is a clever caps-lock, built into ZMK. Typing out constants 173 - such as <code>PORT</code> with home-row mods would look like this:</p> 174 - <ul> 175 - <li>hold <code>e</code> (shift) on left hand, and tap <code>p</code> on 176 - right hand</li> 177 - <li>hold <code>e</code> (shift) on left hand, and tap <code>o</code> on 178 - right hand</li> 179 - <li>hold <code>s</code> (shift) on right hand, and tap <code>r</code> on 180 - left hand</li> 181 - <li>hold <code>s</code> (shift) on right hand, and tap <code>t</code> on 182 - left hand</li> 183 - </ul> 184 - <p>This hold-alternate-hold dance gets tiring quickly. With caps-word, 185 - however:</p> 186 - <ul> 187 - <li>toggle <code>caps_word</code></li> 188 - <li>type out <code>p</code>, <code>o</code>, <code>r</code>, 189 - <code>t</code></li> 190 - <li>hit a <em>break</em> character (space, enter will do)</li> 191 - <li>continue</li> 192 - </ul> 193 - <p>Caps-word automatically disables capitalization upon encountering a 194 - breaking character, (which are space, enter or any modifier, by default) 195 - right in the firmware!</p> 196 - <h1 id="findings">Findings</h1> 197 - <p>34-keys has been reasonably comfortable to use, for both prose and 198 - program. My palms do not move across the desk at all, as I reach for 199 - keys. I mostly write Rust and Bash, and my layout has evolved to 200 - accomodate special characters from their grammars (angled brackets and 201 - hyphens, specifically). If you are on a similar journey, I would suggest 202 - focusing on accuracy and comfort over speed. Speed comes with time.</p> 203 - 204 - </div> 205 - 206 - <div class="intro"> 207 - Hi. 208 - <div class="hot-links"> 209 - <a href="/index.xml" class="feed-button">Subscribe</a> 210 - </div> 211 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 212 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 213 - <p>Reach out at oppili@libera.chat.</p> 214 - </div> 215 - 216 - <a href="/" class="post-end-link">Home</a> 217 - <span>/</span> 218 - <a href="/posts" class="post-end-link">Posts</a> 219 - <span>/</span> 220 - <a class="post-end-link">Programming On 34 Keys</a> 221 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/programming_on_34_keys.md 222 - ">View Raw</a> 223 - </div> 224 - </div> 225 - </body> 226 - </html>
-220
docs/posts/rapid_refactoring_with_vim/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Rapid Refactoring With Vim"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/rapid_refactoring_with_vim"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Rapid Refactoring With Vim · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Rapid Refactoring With Vim</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/rapid_refactoring_with_vim.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 31/03 — 2020 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 79.12 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 5.4 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Rapid Refactoring With Vim 43 - </h1> 44 - <div class="post-text"> 45 - <p>Last weekend, I was tasked with refactoring the 96 unit tests on <a 46 - href="https://github.com/ruma/ruma-events/pull/70">ruma-events</a> to 47 - use strictly typed json objects using <code>serde_json::json!</code> 48 - instead of raw strings. It was rather painless thanks to vim :)</p> 49 - <p>Here’s a small sample of what had to be done (note the lines prefixed 50 - with the arrow):</p> 51 - <div class="sourceCode" id="cb1"><pre 52 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a>→ <span class="kw">use</span> <span class="pp">serde_json::</span><span class="op">{</span>from_str<span class="op">};</span></span> 53 - <span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a> </span> 54 - <span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="at">#[</span>test<span class="at">]</span></span> 55 - <span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">fn</span> deserialize() <span class="op">{</span></span> 56 - <span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a> <span class="pp">assert_eq!</span>(</span> 57 - <span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a>→ <span class="pp">from_str::</span><span class="op">&lt;</span>Action<span class="op">&gt;</span>(<span class="st">r#&quot;{&quot;set_tweak&quot;: &quot;highlight&quot;}&quot;#</span>)<span class="op">,</span></span> 58 - <span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="pp">Action::</span>SetTweak(<span class="pp">Tweak::</span>Highlight <span class="op">{</span> value<span class="op">:</span> <span class="cn">true</span> <span class="op">}</span>)</span> 59 - <span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a> )<span class="op">;</span></span> 60 - <span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span></code></pre></div> 61 - <p>had to be converted to:</p> 62 - <div class="sourceCode" id="cb2"><pre 63 - class="sourceCode rust"><code class="sourceCode rust"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a>→ <span class="kw">use</span> <span class="pp">serde_json::</span><span class="op">{</span>from_value<span class="op">};</span></span> 64 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a> </span> 65 - <span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a> <span class="at">#[</span>test<span class="at">]</span></span> 66 - <span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a> <span class="kw">fn</span> deserialize() <span class="op">{</span></span> 67 - <span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a> <span class="pp">assert_eq!</span>(</span> 68 - <span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a>→ <span class="pp">from_value::</span><span class="op">&lt;</span>Action<span class="op">&gt;</span>(<span class="pp">json!</span>(<span class="op">{</span><span class="st">&quot;set_tweak&quot;</span><span class="op">:</span> <span class="st">&quot;highlight&quot;</span><span class="op">}</span>))<span class="op">,</span></span> 69 - <span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a> <span class="pp">Action::</span>SetTweak(<span class="pp">Tweak::</span>Highlight <span class="op">{</span> value<span class="op">:</span> <span class="cn">true</span> <span class="op">}</span>)</span> 70 - <span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a> )<span class="op">;</span></span> 71 - <span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a> <span class="op">}</span></span></code></pre></div> 72 - <h3 id="the-arglist">The arglist</h3> 73 - <p>For the initial pass, I decided to handle imports, this was a simple 74 - find and replace operation, done to all the files containing tests. 75 - Luckily, modules (and therefore files) containing tests in Rust are 76 - annotated with the <code>#[cfg(test)]</code> attribute. I opened all 77 - such files:</p> 78 - <div class="sourceCode" id="cb3"><pre 79 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="co"># `grep -l pattern files` lists all the files</span></span> 80 - <span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="co"># matching the pattern</span></span> 81 - <span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a></span> 82 - <span id="cb3-4"><a href="#cb3-4" aria-hidden="true" tabindex="-1"></a><span class="ex">vim</span> <span class="va">$(</span><span class="fu">grep</span> <span class="at">-l</span> <span class="st">&#39;cfg\(test\)&#39;</span> ./<span class="pp">**</span>/<span class="pp">*</span>.rs<span class="va">)</span></span> 83 - <span id="cb3-5"><a href="#cb3-5" aria-hidden="true" tabindex="-1"></a></span> 84 - <span id="cb3-6"><a href="#cb3-6" aria-hidden="true" tabindex="-1"></a><span class="co"># expands to something like:</span></span> 85 - <span id="cb3-7"><a href="#cb3-7" aria-hidden="true" tabindex="-1"></a><span class="ex">vim</span> push_rules.rs room/member.rs key/verification/lib.rs</span></code></pre></div> 86 - <p>Starting vim with more than one file at the shell prompt populates 87 - the arglist. Hit <code>:args</code> to see the list of files currently 88 - ready to edit. The square [brackets] indicate the current file. Navigate 89 - through the arglist with <code>:next</code> and <code>:prev</code>. I 90 - use tpope’s vim-unimpaired <a href="#fn1" class="footnote-ref" 91 - id="fnref1" role="doc-noteref"><sup>1</sup></a>, which adds 92 - <code>]a</code> and <code>[a</code>, mapped to <code>:next</code> and 93 - <code>:prev</code>.</p> 94 - <p>All that’s left to do is the find and replace, for which we will be 95 - using vim’s <code>argdo</code>, applying a substitution to every file in 96 - the arglist:</p> 97 - <pre><code>:argdo s/from_str/from_value/g</code></pre> 98 - <h3 id="the-quickfix-list">The quickfix list</h3> 99 - <p>Next up, replacing <code>r#" ... "#</code> with 100 - <code>json!( ... )</code>. I couldn’t search and replace that trivially, 101 - so I went with a macro call <a href="#fn2" class="footnote-ref" 102 - id="fnref2" role="doc-noteref"><sup>2</sup></a> instead, starting with 103 - the cursor on ‘r’, represented by the caret, in my attempt to breakdown 104 - the process:</p> 105 - <pre><code>BUFFER: r#&quot; ... &quot;#; 106 - ^ 107 - 108 - ACTION: vllsjson!( 109 - 110 - BUFFER json!( ... &quot;#; 111 - ^ 112 - 113 - ACTION: &lt;esc&gt;$F# 114 - 115 - BUFFER: json!( ... &quot;#; 116 - ^ 117 - 118 - ACTION: vhs)&lt;esc&gt; 119 - 120 - BUFFER: json!( ... );</code></pre> 121 - <p>Here’s the recorded <a href="#fn3" class="footnote-ref" id="fnref3" 122 - role="doc-noteref"><sup>3</sup></a> macro in all its glory: 123 - <code>vllsjson!(&lt;esc&gt;$F#vhs)&lt;esc&gt;</code>.</p> 124 - <p>Great! So now we just go ahead, find every occurrence of 125 - <code>r#</code> and apply the macro right? Unfortunately, there were 126 - more than a few occurrences of raw strings that had to stay raw strings. 127 - Enter, the quickfix list.</p> 128 - <p>The idea behind the quickfix list is to jump from one position in a 129 - file to another (maybe in a different file), much like how the arglist 130 - lets you jump from one file to another.</p> 131 - <p>One of the easiest ways to populate this list with a bunch of 132 - positions is to use <code>vimgrep</code>:</p> 133 - <pre><code># basic usage 134 - :vimgrep pattern files 135 - 136 - # search for raw strings 137 - :vimgrep &#39;r#&#39; ./**/*.rs</code></pre> 138 - <p>Like <code>:next</code> and <code>:prev</code>, you can navigate the 139 - quickfix list with <code>:cnext</code> and <code>:cprev</code>. Every 140 - time you move up or down the list, vim indicates your index:</p> 141 - <pre><code>(1 of 131): r#&quot;{&quot;set_tweak&quot;: &quot;highlight&quot;}&quot;#;</code></pre> 142 - <p>And just like <code>argdo</code>, you can <code>cdo</code> to apply 143 - commands to <em>every</em> match in the quickfix list:</p> 144 - <pre><code>:cdo norm! @q</code></pre> 145 - <p>But, I had to manually pick out matches, and it involved some button 146 - mashing.</p> 147 - <h3 id="external-filtering">External Filtering</h3> 148 - <p>Some code reviews later, I was asked to format all the json inside 149 - the <code>json!</code> macro. All you have to do is pass a visual 150 - selection through a pretty json printer. Select the range to be 151 - formatted in visual mode, and hit <code>:</code>, you will notice the 152 - command line displaying what seems to be gibberish:</p> 153 - <pre><code>:&#39;&lt;,&#39;&gt;</code></pre> 154 - <p><code>'&lt;</code> and <code>'&gt;</code> are <em>marks</em> <a 155 - href="#fn4" class="footnote-ref" id="fnref4" 156 - role="doc-noteref"><sup>4</sup></a>. More specifically, they are marks 157 - that vim sets automatically every time you make a visual selection, 158 - denoting the start and end of the selection.</p> 159 - <p>A range is one or more line specifiers separated by a 160 - <code>,</code>:</p> 161 - <pre><code>:1,7 lines 1 through 7 162 - :32 just line 32 163 - :. the current line 164 - :.,$ the current line to the last line 165 - :&#39;a,&#39;b mark &#39;a&#39; to mark &#39;b&#39;</code></pre> 166 - <p>Most <code>:</code> commands can be prefixed by ranges. 167 - <code>:help usr_10.txt</code> for more on that.</p> 168 - <p>Alright, lets pass json through <code>python -m json.tool</code>, a 169 - json formatter that accepts <code>stdin</code> (note the use of 170 - <code>!</code> to make use of an external program):</p> 171 - <pre><code>:&#39;&lt;,&#39;&gt;!python -m json.tool</code></pre> 172 - <p>Unfortunately that didn’t quite work for me because the range 173 - included some non-json text as well, a mix of regex and macros helped 174 - fix that. I think you get the drift.</p> 175 - <p>Another fun filter I use from time to time is <code>:!sort</code>, to 176 - sort css attributes, or <code>:!uniq</code> to remove repeated 177 - imports.</p> 178 - <section id="footnotes" class="footnotes footnotes-end-of-document" 179 - role="doc-endnotes"> 180 - <hr /> 181 - <ol> 182 - <li id="fn1"><p>https://github.com/tpope/vim-unimpaired It also handles 183 - various other mappings, <code>]q</code> and <code>[q</code> to navigate 184 - the quickfix list for example<a href="#fnref1" class="footnote-back" 185 - role="doc-backlink">↩︎</a></p></li> 186 - <li id="fn2"><p><code>:help recording</code><a href="#fnref2" 187 - class="footnote-back" role="doc-backlink">↩︎</a></p></li> 188 - <li id="fn3"><p>When I’m recording a macro, I prefer starting out by 189 - storing it in register <code>q</code>, and then copying it over to 190 - another register if it works as intended. I think of <code>qq</code> as 191 - ‘quick record’.<a href="#fnref3" class="footnote-back" 192 - role="doc-backlink">↩︎</a></p></li> 193 - <li id="fn4"><p><code>:help mark-motions</code><a href="#fnref4" 194 - class="footnote-back" role="doc-backlink">↩︎</a></p></li> 195 - </ol> 196 - </section> 197 - 198 - </div> 199 - 200 - <div class="intro"> 201 - Hi. 202 - <div class="hot-links"> 203 - <a href="/index.xml" class="feed-button">Subscribe</a> 204 - </div> 205 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 206 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 207 - <p>Reach out at oppili@libera.chat.</p> 208 - </div> 209 - 210 - <a href="/" class="post-end-link">Home</a> 211 - <span>/</span> 212 - <a href="/posts" class="post-end-link">Posts</a> 213 - <span>/</span> 214 - <a class="post-end-link">Rapid Refactoring With Vim</a> 215 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/rapid_refactoring_with_vim.md 216 - ">View Raw</a> 217 - </div> 218 - </div> 219 - </body> 220 - </html>
-227
docs/posts/self-hosting_git/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Self-hosting Git"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/self-hosting_git"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Self-hosting Git · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Self-hosting Git</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/self-hosting_git.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 17/10 — 2020 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 87.91 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 5.4 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Self-hosting Git 43 - </h1> 44 - <div class="post-text"> 45 - <p>Earlier this week, I began migrating my repositories from Github to 46 - <a href="https://git.zx2c4.com/cgit/about/">cgit</a>. If you care at all 47 - about big corporates turning open-source into a T-shirt farming service, 48 - this is the way to go.</p> 49 - <h3 id="offerings">Offerings</h3> 50 - <p>cgit is <em>very</em> bare bones. It is <a 51 - href="https://tools.ietf.org/html/rfc3875">cgi-based</a> web interface 52 - to git, and nothing more. You may browse repositories, view diffs, 53 - commit logs and even clone via http. If you are looking to replace 54 - Github with cgit, keep in mind that cgit does not handle issues or 55 - pull/merge requests. If people wish to contribute to your work, they 56 - would have to send you a patch via email.</p> 57 - <h3 id="setup">Setup</h3> 58 - <p>Installing cgit is fairly straightforward, if you would like to 59 - compile it from source:</p> 60 - <div class="sourceCode" id="cb1"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="co"># fetch</span></span> 61 - <span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="fu">git</span> clone https://git.zx2c4.com <span class="kw">&amp;&amp;</span> <span class="bu">cd</span> cgit</span> 62 - <span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a><span class="fu">git</span> submodule init</span> 63 - <span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a><span class="fu">git</span> submodule update</span> 64 - <span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span> 65 - <span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="co"># install</span></span> 66 - <span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a><span class="fu">make</span> NO_LUA=1</span> 67 - <span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="fu">sudo</span> make install</span></code></pre></div> 68 - <p>This would drop the cgit cgi script (and the default css) into 69 - <code>/var/www/htdocs/cgit</code>. You may configure cgit by editing 70 - <code>/etc/cgitrc</code>. I specify the <code>NO_LUA</code> flag to 71 - compile without lua support, exclude that flag if you would like to 72 - extend cgit via lua scripts.</p> 73 - <h3 id="going-live">Going live</h3> 74 - <p>You might want to use, <a 75 - href="https://github.com/gnosek/fcgiwrap">fcgiwrap</a>, a <a 76 - href="http://www.nongnu.org/fastcgi">fastcgi</a> wrapper for 77 - <code>cgi</code> scripts,</p> 78 - <div class="sourceCode" id="cb2"><pre class="sourceCode sh"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="fu">sudo</span> apt install fcgiwrap</span> 79 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="fu">sudo</span> systemctl start fcgiwrap.socket</span></code></pre></div> 80 - <p>Expose the cgit cgi script to the web via <code>nginx</code>:</p> 81 - <pre><code># nginx.conf 82 - server { 83 - listen 80; 84 - server_name git.example.com; 85 - 86 - # serve static files 87 - location ~* ^.+\.(css|png|ico)$ { 88 - root /var/www/htdocs/cgit; 89 - } 90 - 91 - location / { 92 - fastcgi_pass unix:/run/fcgiwrap.socket; 93 - fastcgi_param SCRIPT_FILENAME /var/www/htdocs/cgit/cgit.cgi; # the default location of the cgit cgi script 94 - fastcgi_param PATH_INFO $uri; 95 - fastcgi_param QUERY_STRING $args; 96 - } 97 - }</code></pre> 98 - <p>Point cgit to your git repositories:</p> 99 - <pre><code># /etc/cgitrc 100 - scan-path=/path/to/git/repos</code></pre> 101 - <p><strong><em>Note</em></strong>: <em><code>scan-path</code> works best 102 - if you stick it at the end of your <code>cgitrc</code></em>.</p> 103 - <p>You may now create remote repositories at 104 - <code>/path/to/git/repos</code>, via:</p> 105 - <pre><code>git init --bare</code></pre> 106 - <p>Add the remote to your local repository:</p> 107 - <pre><code>git remote set-url origin user@remote:/above/path 108 - git push origin master</code></pre> 109 - <h3 id="configuration">Configuration</h3> 110 - <p>cgit is fairly easy to configure, all configuration options can be 111 - found <a href="https://git.zx2c4.com/cgit/tree/cgitrc.5.txt">in the 112 - manual</a>, here are a couple of cool ones though:</p> 113 - <p><strong>enable-commit-graph</strong>: Generates a text based 114 - graphical representation of the commit history, similar to 115 - <code>git log --graph --oneline</code>.</p> 116 - <pre><code>| * | Add support for configuration file 117 - * | | simplify command parsing logic 118 - * | | Refactor parsers 119 - * | | Add basic tests 120 - * | | Merge remote-tracking branch &#39;origin/master&#39; in... 121 - |\| | 122 - | * | add installation instructions for nix 123 - | * | switch to pancurses backendv0.2.2 124 - | * | bump to v0.2.2 125 - * | | Merge branch &#39;master&#39; into feature/larger-names... 126 - |\| | 127 - | * | enable feature based compilation to support win... 128 - | * | remove dependency on rustc v1.45, bump to v0.2.... 129 - | * | Merge branch &#39;feature/windows&#39; of https://git... 130 - | |\ \ 131 - | | * | add windows to github actions 132 - | | * | switch to crossterm backend 133 - | | * | Merge branch &#39;fix/duplicate-habits&#39; 134 - | | |\ \ 135 - | | | * | move duplicate check to command parsing blo...</code></pre> 136 - <p><strong>section-from-path</strong>: This option paired with 137 - <code>scan-path</code> will automatically generate sections in your cgit 138 - index page, from the path to each repo. For example, the directory 139 - structure used to generate sections on <a href="https://git.peppe.rs">my 140 - cgit instance</a> looks like this:</p> 141 - <pre><code>├── cli 142 - │ ├── dijo 143 - │ ├── eva 144 - │ ├── pista 145 - │ ├── taizen 146 - │ └── xcursorlocate 147 - ├── config 148 - │ ├── dotfiles 149 - │ └── nixos 150 - ├── fonts 151 - │ ├── curie 152 - │ └── scientifica 153 - ├── languages 154 - │ └── lisk 155 - ├── libs 156 - │ ├── cutlass 157 - │ └── fondant 158 - ├── terminfo 159 - ├── university 160 - │ └── furby 161 - └── web 162 - └── isostatic</code></pre> 163 - <h3 id="ease-of-use">Ease of use</h3> 164 - <p>As I mentioned before, <code>cgit</code> is simply a view into your 165 - git repositories, you will have to manually create new repositories by 166 - entering your remote and using <code>git init --bare</code>. Here are a 167 - couple of scripts I wrote to perform actions on remotes, think of it as 168 - a smaller version of Github’s <code>gh</code> program.</p> 169 - <p>You may save these scripts as <code>git-script-name</code> and drop 170 - them in your <code>$PATH</code>, and git will automatically add an alias 171 - called <code>script-name</code>, callable via:</p> 172 - <pre><code>git script-name</code></pre> 173 - <h4 id="git-new-repo">git-new-repo</h4> 174 - <p>Creates a new repository on your remote, the first arg may be a path 175 - (section/repo-name) or just the repo name:</p> 176 - <pre><code>#! /usr/bin/env bash 177 - # 178 - # usage: 179 - # git new-repo section/repo-name 180 - # 181 - # example: 182 - # git new-repo fonts/scientifica 183 - # creates: user@remote:fonts/scientifica 184 - 185 - if [ $# -eq 0 ]; then 186 - echo &quot;requires an arg&quot; 187 - exit 1 188 - fi 189 - 190 - ssh user@remote git init --bare &quot;$1&quot;;</code></pre> 191 - <h4 id="git-set-desc">git-set-desc</h4> 192 - <p>To set a one line repository description. It simply copies the local 193 - <code>.git/description</code>, into <code>remote/description</code>. 194 - <code>cgit</code> displays the contents of this file on the index 195 - page:</p> 196 - <pre><code>#! /usr/bin/env bash 197 - # 198 - # usage: 199 - # enter repo description into .git/description and run: 200 - # git set-desc 201 - 202 - remote=$(git remote get-url --push origin) 203 - scp .git/description &quot;$remote/description&quot;</code></pre> 204 - 205 - </div> 206 - 207 - <div class="intro"> 208 - Hi. 209 - <div class="hot-links"> 210 - <a href="/index.xml" class="feed-button">Subscribe</a> 211 - </div> 212 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 213 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 214 - <p>Reach out at oppili@libera.chat.</p> 215 - </div> 216 - 217 - <a href="/" class="post-end-link">Home</a> 218 - <span>/</span> 219 - <a href="/posts" class="post-end-link">Posts</a> 220 - <span>/</span> 221 - <a class="post-end-link">Self-hosting Git</a> 222 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/self-hosting_git.md 223 - ">View Raw</a> 224 - </div> 225 - </div> 226 - </body> 227 - </html>
-135
docs/posts/snip_snap/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Snip Snap"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/snip_snap"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Snip Snap · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Snip Snap</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/snip_snap.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 29/05 — 2024 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 40.76 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 2.1 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Snip Snap 43 - </h1> 44 - <div class="post-text"> 45 - <p>I regularly switch between exactly two things while working, a 46 - “current” and an “alternate” item; a lot of tools I use seem to support 47 - this flow.</p> 48 - <h4 id="git">git</h4> 49 - <p>Pass <code>-</code> to <code>git-checkout</code> to switch to the 50 - previously active branch:</p> 51 - <div class="sourceCode" id="cb1"><pre 52 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> git branch</span> 53 - <span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="ex">*</span> foo</span> 54 - <span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="ex">bar</span></span> 55 - <span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a></span> 56 - <span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> git checkout bar</span> 57 - <span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> git branch</span> 58 - <span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="ex">foo</span></span> 59 - <span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="ex">*</span> bar</span> 60 - <span id="cb1-9"><a href="#cb1-9" aria-hidden="true" tabindex="-1"></a></span> 61 - <span id="cb1-10"><a href="#cb1-10" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> git checkout <span class="at">-</span></span> 62 - <span id="cb1-11"><a href="#cb1-11" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> git branch</span> 63 - <span id="cb1-12"><a href="#cb1-12" aria-hidden="true" tabindex="-1"></a><span class="ex">*</span> foo</span> 64 - <span id="cb1-13"><a href="#cb1-13" aria-hidden="true" tabindex="-1"></a> <span class="ex">bar</span></span></code></pre></div> 65 - <h4 id="bash---cd">bash - cd</h4> 66 - <p>This may not be exclusive to <code>bash</code>:</p> 67 - <div class="sourceCode" id="cb2"><pre 68 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="ex">~/foo</span> $ cd ~/bar</span> 69 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="ex">~/bar</span> $ cd <span class="at">-</span></span> 70 - <span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a><span class="ex">~/foo</span> $</span></code></pre></div> 71 - <p>This is especially handy in combination with my <a 72 - href="../curing_a_case_of_git-UX/">git-worktree flow</a>:</p> 73 - <div class="sourceCode" id="cb3"><pre 74 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb3-1"><a href="#cb3-1" aria-hidden="true" tabindex="-1"></a><span class="ex">~/main-branch</span> $ gwj feature</span> 75 - <span id="cb3-2"><a href="#cb3-2" aria-hidden="true" tabindex="-1"></a><span class="ex">~/feat-branch</span> $ cd <span class="at">-</span></span> 76 - <span id="cb3-3"><a href="#cb3-3" aria-hidden="true" tabindex="-1"></a><span class="ex">~/main-branch</span> $</span></code></pre></div> 77 - <h4 id="bash---jobs">bash - jobs</h4> 78 - <p>I often suspend multiple <code>vim</code> sessions with 79 - <code>Ctrl-Z</code>:</p> 80 - <div class="sourceCode" id="cb4"><pre 81 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb4-1"><a href="#cb4-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> jobs</span> 82 - <span id="cb4-2"><a href="#cb4-2" aria-hidden="true" tabindex="-1"></a><span class="ex">[1]+</span> Stopped vim transpiler/src/transform.rs</span> 83 - <span id="cb4-3"><a href="#cb4-3" aria-hidden="true" tabindex="-1"></a><span class="ex">[2]-</span> Stopped git commit <span class="at">--verbose</span></span></code></pre></div> 84 - <p>In the above example: I suspended <code>vim</code> when working on 85 - <code>transform.rs</code>, and then began working on a commit by running 86 - <code>git commit</code> without a message flag (lets you craft a message 87 - in <code>$EDITOR</code>). To bring the current job to the foreground, 88 - you can use <code>fg</code>:</p> 89 - <div class="sourceCode" id="cb5"><pre 90 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb5-1"><a href="#cb5-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> fg</span></code></pre></div> 91 - <p>With a job identifier:</p> 92 - <div class="sourceCode" id="cb6"><pre 93 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb6-1"><a href="#cb6-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> fg %2 <span class="co"># resumes interactive git commit</span></span></code></pre></div> 94 - <p>Or switch to “last” job, or the second-most-recently-resumed job:</p> 95 - <div class="sourceCode" id="cb7"><pre 96 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb7-1"><a href="#cb7-1" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> fg %-</span> 97 - <span id="cb7-2"><a href="#cb7-2" aria-hidden="true" tabindex="-1"></a><span class="ex">$</span> %- <span class="co"># shorthand</span></span></code></pre></div> 98 - <h4 id="vim">vim</h4> 99 - <p>Switch to the last active buffer with <code>Ctrl+^</code>. In 100 - command-mode, <code>#</code> refers to the last active buffer, you can 101 - use this as an argument to a few commands:</p> 102 - <pre class="vimscript"><code>:b# &quot; switch to alternate buffer (same as Ctrl+^) 103 - :vsp# &quot; create a vertical split with the alternate buffer 104 - :read# &quot; read contents of alternate buffer into current buffer 105 - :!wc # &quot; pass file name of alternate buffer to the command `wc`</code></pre> 106 - <p>See <code>:help c_#</code> for more.</p> 107 - <h4 id="tmux">tmux</h4> 108 - <p>Switch to the last active tmux session with 109 - <code>&lt;prefix&gt;+shift+L</code>.</p> 110 - <h4 id="qutebrowser">qutebrowser</h4> 111 - <p>Switch to the last active tab with <code>g$</code>.</p> 112 - 113 - </div> 114 - 115 - <div class="intro"> 116 - Hi. 117 - <div class="hot-links"> 118 - <a href="/index.xml" class="feed-button">Subscribe</a> 119 - </div> 120 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 121 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 122 - <p>Reach out at oppili@libera.chat.</p> 123 - </div> 124 - 125 - <a href="/" class="post-end-link">Home</a> 126 - <span>/</span> 127 - <a href="/posts" class="post-end-link">Posts</a> 128 - <span>/</span> 129 - <a class="post-end-link">Snip Snap</a> 130 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/snip_snap.md 131 - ">View Raw</a> 132 - </div> 133 - </div> 134 - </body> 135 - </html>
-113
docs/posts/static_sites_with_bash/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Static Sites With Bash"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/static_sites_with_bash"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Static Sites With Bash · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Static Sites With Bash</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/static_sites_with_bash.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 22/11 — 2019 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 21.18 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 1.5 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Static Sites With Bash 43 - </h1> 44 - <div class="post-text"> 45 - <p>After going through a bunch of static site generators (<a 46 - href="https://blog.getpelican.com/">pelican</a>, <a 47 - href="https://gohugo.io">hugo</a>, <a 48 - href="https://github.com/icyphox/vite">vite</a>), I decided to roll my 49 - own. If you are more of the ‘show me the code’ kinda guy, <a 50 - href="https://github.com/nerdypepper/site">here</a> you go.</p> 51 - <h3 id="text-formatting">Text formatting</h3> 52 - <p>I chose to write in markdown, and convert to html with <a 53 - href="https://kristaps.bsd.lv/lowdown/">lowdown</a>.</p> 54 - <h3 id="directory-structure">Directory structure</h3> 55 - <p>I host my site on GitHub pages, so <code>docs/</code> has to be the 56 - entry point. Markdown formatted posts go into <code>posts/</code>, get 57 - converted into html, and end up in <code>docs/index.html</code>, 58 - something like this:</p> 59 - <div class="sourceCode" id="cb1"><pre 60 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb1-1"><a href="#cb1-1" aria-hidden="true" tabindex="-1"></a><span class="va">posts</span><span class="op">=</span><span class="va">$(</span><span class="fu">ls</span> <span class="at">-t</span> ./posts<span class="va">)</span> <span class="co"># chronological order!</span></span> 61 - <span id="cb1-2"><a href="#cb1-2" aria-hidden="true" tabindex="-1"></a><span class="cf">for</span> f <span class="kw">in</span> <span class="va">$posts</span><span class="kw">;</span> <span class="cf">do</span></span> 62 - <span id="cb1-3"><a href="#cb1-3" aria-hidden="true" tabindex="-1"></a> <span class="va">file</span><span class="op">=</span><span class="st">&quot;./posts/&quot;</span><span class="va">$f</span> <span class="co"># `ls` mangled our file paths</span></span> 63 - <span id="cb1-4"><a href="#cb1-4" aria-hidden="true" tabindex="-1"></a> <span class="bu">echo</span> <span class="st">&quot;generating post </span><span class="va">$file</span><span class="st">&quot;</span></span> 64 - <span id="cb1-5"><a href="#cb1-5" aria-hidden="true" tabindex="-1"></a></span> 65 - <span id="cb1-6"><a href="#cb1-6" aria-hidden="true" tabindex="-1"></a> <span class="va">html</span><span class="op">=</span><span class="va">$(</span><span class="ex">lowdown</span> <span class="st">&quot;</span><span class="va">$file</span><span class="st">&quot;</span><span class="va">)</span></span> 66 - <span id="cb1-7"><a href="#cb1-7" aria-hidden="true" tabindex="-1"></a> <span class="bu">echo</span> <span class="at">-e</span> <span class="st">&quot;html&quot;</span> <span class="op">&gt;&gt;</span> docs/index.html</span> 67 - <span id="cb1-8"><a href="#cb1-8" aria-hidden="true" tabindex="-1"></a><span class="cf">done</span></span></code></pre></div> 68 - <h3 id="assets">Assets</h3> 69 - <p>Most static site generators recommend dropping image assets into the 70 - site source itself. That does have it’s merits, but I prefer hosting 71 - images separately:</p> 72 - <div class="sourceCode" id="cb2"><pre 73 - class="sourceCode bash"><code class="sourceCode bash"><span id="cb2-1"><a href="#cb2-1" aria-hidden="true" tabindex="-1"></a><span class="co"># strip file extension</span></span> 74 - <span id="cb2-2"><a href="#cb2-2" aria-hidden="true" tabindex="-1"></a><span class="va">ext</span><span class="op">=</span><span class="st">&quot;</span><span class="va">${1</span><span class="op">##</span><span class="pp">*</span>.<span class="va">}</span><span class="st">&quot;</span></span> 75 - <span id="cb2-3"><a href="#cb2-3" aria-hidden="true" tabindex="-1"></a></span> 76 - <span id="cb2-4"><a href="#cb2-4" aria-hidden="true" tabindex="-1"></a><span class="co"># generate a random file name</span></span> 77 - <span id="cb2-5"><a href="#cb2-5" aria-hidden="true" tabindex="-1"></a><span class="va">id</span><span class="op">=</span><span class="va">$(</span> <span class="fu">cat</span> /dev/urandom <span class="kw">|</span> <span class="fu">tr</span> <span class="at">-dc</span> <span class="st">&#39;a-zA-Z0-9&#39;</span> <span class="kw">|</span> <span class="fu">fold</span> <span class="at">-w</span> 2 <span class="kw">|</span> <span class="fu">head</span> <span class="at">-n</span> 1 <span class="va">)</span></span> 78 - <span id="cb2-6"><a href="#cb2-6" aria-hidden="true" tabindex="-1"></a><span class="va">id</span><span class="op">=</span><span class="st">&quot;</span><span class="va">$id</span><span class="st">.</span><span class="va">$ext</span><span class="st">&quot;</span></span> 79 - <span id="cb2-7"><a href="#cb2-7" aria-hidden="true" tabindex="-1"></a></span> 80 - <span id="cb2-8"><a href="#cb2-8" aria-hidden="true" tabindex="-1"></a><span class="co"># copy to my file host</span></span> 81 - <span id="cb2-9"><a href="#cb2-9" aria-hidden="true" tabindex="-1"></a><span class="fu">scp</span> <span class="at">-P</span> 443 <span class="st">&quot;</span><span class="va">$1</span><span class="st">&quot;</span> emerald:files/<span class="st">&quot;</span><span class="va">$id</span><span class="st">&quot;</span> </span> 82 - <span id="cb2-10"><a href="#cb2-10" aria-hidden="true" tabindex="-1"></a><span class="bu">echo</span> <span class="st">&quot;https://u.peppe.rs/</span><span class="va">$id</span><span class="st">&quot;</span></span></code></pre></div> 83 - <h3 id="templating">Templating</h3> 84 - <p><a 85 - href="https://github.com/NerdyPepper/site/blob/master/generate.sh"><code>generate.sh</code></a> 86 - brings the above bits and pieces together (with some extra cruft to 87 - avoid javascript). It uses <code>sed</code> to produce nice titles from 88 - the file names (removes underscores, title-case), and 89 - <code>date(1)</code> to add the date to each post listing!</p> 90 - 91 - </div> 92 - 93 - <div class="intro"> 94 - Hi. 95 - <div class="hot-links"> 96 - <a href="/index.xml" class="feed-button">Subscribe</a> 97 - </div> 98 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 99 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 100 - <p>Reach out at oppili@libera.chat.</p> 101 - </div> 102 - 103 - <a href="/" class="post-end-link">Home</a> 104 - <span>/</span> 105 - <a href="/posts" class="post-end-link">Posts</a> 106 - <span>/</span> 107 - <a class="post-end-link">Static Sites With Bash</a> 108 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/static_sites_with_bash.md 109 - ">View Raw</a> 110 - </div> 111 - </div> 112 - </body> 113 - </html>
-200
docs/posts/tales_from_mainframe_modernization/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Tales From Mainframe Modernization"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/tales_from_mainframe_modernization"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Tales From Mainframe Modernization · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Tales From Mainframe Modernization</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/tales_from_mainframe_modernization.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 21/05 — 2025 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 65.93 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 3.9 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Tales From Mainframe Modernization 43 - </h1> 44 - <div class="post-text"> 45 - <p>At my last workplace, I wrote transpilers (or just <a 46 - href="https://people.csail.mit.edu/rachit/post/transpiler/">compilers</a> 47 - if you prefer) from mainframe languages (COBOL, JCL, BASIC etc.) to Java 48 - (in Rust!).</p> 49 - <p>Legacy code is full of surprises. In the roughly 200k lines of COBOL 50 - that I had the (dis)pleasure of working with, I saw some wonderful hacks 51 - to get around the limitations of the system. Mainframes are also chock 52 - full of history.</p> 53 - <h3 id="base-10-numerics">Base-10 numerics</h3> 54 - <p>This is the first thing that stood out to me when I looked at COBOL 55 - code, a data-definition (the phrase for “variable”) in COBOL is declared 56 - like so:</p> 57 - <pre><code> ,-- name 58 - | ,- type 59 - __|___ __|_ 60 - 01 HEIGHT PIC 9(3). 61 - -- --- 62 - | | 63 - | `- picture clause (keyword) 64 - `- level number </code></pre> 65 - <p>That statement declares a variable called <code>HEIGHT</code> with 66 - type <code>9(3)</code>, which is shorthand for <code>999</code>, which 67 - indicates “3-digit number”. The possible values for this variable are 68 - <code>0</code> to <code>999</code>!</p> 69 - <h3 id="internationalisation">Internationalisation</h3> 70 - <p>Below is another data-definition in COBOL, declaring 3 variables:</p> 71 - <pre class="cobol"><code>01 FOO-PERSON. 72 - 05 FOO-NAME PIC X(5). 73 - 05 FOO-HEIGHT PIC 9(3).</code></pre> 74 - <p>What that means is:</p> 75 - <ul> 76 - <li><code>FOO-PERSON</code>: a “group” variable consisting of two other 77 - variables</li> 78 - <li><code>FOO-NAME</code>: an alphanumeric type with 5 characters</li> 79 - <li><code>FOO-HEIGHT</code>: a numeric type with 3 digits (remember, 80 - base 10 and not base 2)</li> 81 - </ul> 82 - <p>COBOL has an interesting construct called “REDEFINES”:</p> 83 - <pre class="cobol"><code>01 FOO-PERSON. 84 - 05 FOO-NAME PIC X(5). 85 - 05 FOO-HEIGHT PIC 9(3). 86 - 87 - 01 FOO-PERSONNE REDEFINES FOO-PERSON. 88 - 05 FOO-NOM PIC X(5). 89 - 05 FOO-TAILLE PIC 9(3).</code></pre> 90 - <p><code>FOO-PERSON</code> and <code>FOO-PERSONNE</code> refer to the 91 - same region of memory.</p> 92 - <p>I helped modernise a codebase that had clearly been worked on by a 93 - Spanish consultancy at some point, and they had decided to redefine all 94 - data definitions in Spanish.</p> 95 - <h3 id="string-parsing">String parsing</h3> 96 - <p>Here’s another fun one:</p> 97 - <pre class="cobol"><code> 01 FOO-PERSON. 98 - 05 FOO-NAME PIC X(5). 99 - 05 FOO-HEIGHT PIC 9(3). 100 - . 101 - . 102 - . 103 - 104 - MOVE &quot;PETER&quot; TO FOO-NAME. 105 - MOVE 175 TO FOO-HEIGHT. 106 - 107 - *&gt; display the entire memory region 108 - DISPLAY FOO-PERSON. 109 - *&gt; PETER175 110 - 111 - *&gt; subscripting the first 7 bytes... 112 - DISPLAY FOO-PERSON (1:7) 113 - *&gt; PETER17</code></pre> 114 - <p>So data-definitions simply describe names for regions. Which enables 115 - a clever way to parse strings:</p> 116 - <pre class="cobol"><code> 01 DATE. 117 - 05 DD PIC 9(2). 118 - 05 FILLER PIC X. 119 - 05 MMM PIC A(3). 120 - 05 FILLER PIC X. 121 - 05 YYYY PIC 9(4). 122 - 123 - . 124 - . 125 - . 126 - 127 - MOVE &quot;03 MAR 2025&quot; TO DATE. 128 - DISPLAY &quot;DAY: &quot; DD. *&gt; DAY: 03 129 - DISPLAY &quot;MONTH: &quot; MMM. *&gt; MONTH: MAR 130 - DISPLAY &quot;YEAR: &quot; YYYY. *&gt; YEAR: 2025 131 - 132 - *&gt; also works: 133 - MOVE &quot;03-MAR-2025&quot; TO DATE.</code></pre> 134 - <h3 id="early-exit">Early exit</h3> 135 - <p>I’d see this peppered around in a few places; which I later realized 136 - was a way to trigger an abnormal end to a batch job (possibly triggering 137 - an error handling routine in the outer job control system):</p> 138 - <pre class="cobol"><code> 01 CONSTANT-ZERO S9(9)V9 VALUE 0. 139 - 01 ABEND S9(9)V9. 140 - 141 - . 142 - . 143 - . 144 - 145 - COMPUTE ABEND = CONSTANT-ZERO / CONSTANT-ZERO.</code></pre> 146 - <h3 id="all-the-numbers">All the numbers</h3> 147 - <p>I have yet to find an explanation for this one, but I once found a 148 - file with just the first 800 natural numbers defined as string 149 - constants:</p> 150 - <pre class="cobol"><code> 01 TC0001 X(5) &quot;00001&quot;. 151 - 01 TC0002 X(5) &quot;00002&quot;. 152 - 01 TC0003 X(5) &quot;00003&quot;. 153 - . 154 - . 155 - *&gt; .... 800 lines later .... 156 - . 157 - . 158 - 01 TC0800 X(5) &quot;00800&quot;.</code></pre> 159 - <p>The file was definitely not generated, and I can’t imagine text 160 - editors on the mainframe were all that advanced either.</p> 161 - <h3 id="dd---disk-destroyer"><code>dd</code> - disk destroyer</h3> 162 - <p>The <code>DD</code> statement in the JCL subsystem stands for “data 163 - definition”, which is largely used to describe files and IO streams used 164 - by a batch job. The <code>dd</code> command <a href="#fn1" 165 - class="footnote-ref" id="fnref1" role="doc-noteref"><sup>1</sup></a> on 166 - UNIX is named after this statement!</p> 167 - <section id="footnotes" class="footnotes footnotes-end-of-document" 168 - role="doc-endnotes"> 169 - <hr /> 170 - <ol> 171 - <li id="fn1"><p><a 172 - href="https://en.wikipedia.org/wiki/Dd_%28Unix%29#History">Wikipedia - 173 - dd (Unix)</a><a href="#fnref1" class="footnote-back" 174 - role="doc-backlink">↩︎</a></p></li> 175 - </ol> 176 - </section> 177 - 178 - </div> 179 - 180 - <div class="intro"> 181 - Hi. 182 - <div class="hot-links"> 183 - <a href="/index.xml" class="feed-button">Subscribe</a> 184 - </div> 185 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 186 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 187 - <p>Reach out at oppili@libera.chat.</p> 188 - </div> 189 - 190 - <a href="/" class="post-end-link">Home</a> 191 - <span>/</span> 192 - <a href="/posts" class="post-end-link">Posts</a> 193 - <span>/</span> 194 - <a class="post-end-link">Tales From Mainframe Modernization</a> 195 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/tales_from_mainframe_modernization.md 196 - ">View Raw</a> 197 - </div> 198 - </div> 199 - </body> 200 - </html>
-101
docs/posts/termux_tandem/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Termux Tandem"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/termux_tandem"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Termux Tandem · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Termux Tandem</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/termux_tandem.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 07/03 — 2020 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 19.18 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 1.6 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Termux Tandem 43 - </h1> 44 - <div class="post-text"> 45 - <p>I learnt about <code>termux</code> from a friend on IRC recently. It 46 - looked super gimmicky to me at first, but it eventually proved to be 47 - useful. Here’s what I use it for:</p> 48 - <h3 id="rsync">rsync</h3> 49 - <p>Ever since I degoogled my android device, syncing files between my 50 - phone and my PC has always been a pain. I’m looking at you MTP. But, 51 - with <code>termux</code> and <code>sshd</code> all set up, it’s as 52 - simple as:</p> 53 - <pre><code>$ arp 54 - Address HWtype HWad ... 55 - 192.168.43.187 ether d0:0 ... 56 - 57 - $ rsync -avz 192.168.43.187:~/frogs ~/pics/frogs</code></pre> 58 - <h3 id="ssh-tmux">ssh &amp; tmux</h3> 59 - <p>My phone doubles as a secondary view into my main machine with 60 - <code>ssh</code> and <code>tmux</code>. When I am away from my PC (read: 61 - sitting across the room), I check build status and IRC messages by 62 - <code>ssh</code>ing into a tmux session running the said build or 63 - weechat.</p> 64 - <h3 id="file-uploads">file uploads</h3> 65 - <p>Not being able to access my (ssh-only) file host was crippling. With 66 - a <code>bash</code> instance on my phone, I just copied over my ssh 67 - keys, and popped in a file upload script (a glorified <code>scp</code>). 68 - Now I just have to figure out a way to clean up these file names …</p> 69 - <pre><code>~/storage/pictures/ $ ls 70 - 02muf5g7b2i41.jpg 7alt3cwg77841.jpg cl4bsrge7id11.png 71 - mtZabXG.jpg p8d5c584f2841.jpg vjUxGjq.jpg</code></pre> 72 - <h3 id="cmus">cmus</h3> 73 - <p>Alright, I don’t really listen to music via <code>cmus</code>, but I 74 - did use it a couple times when my default music player was acting up. 75 - <code>cmus</code> is a viable option:</p> 76 - <p><a href="https://u.peppe.rs/CP.jpg"><img 77 - src="https://u.peppe.rs/CP.jpg" /></a></p> 78 - 79 - </div> 80 - 81 - <div class="intro"> 82 - Hi. 83 - <div class="hot-links"> 84 - <a href="/index.xml" class="feed-button">Subscribe</a> 85 - </div> 86 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 87 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 88 - <p>Reach out at oppili@libera.chat.</p> 89 - </div> 90 - 91 - <a href="/" class="post-end-link">Home</a> 92 - <span>/</span> 93 - <a href="/posts" class="post-end-link">Posts</a> 94 - <span>/</span> 95 - <a class="post-end-link">Termux Tandem</a> 96 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/termux_tandem.md 97 - ">View Raw</a> 98 - </div> 99 - </div> 100 - </body> 101 - </html>
-91
docs/posts/turing_complete_type_systems/index.html
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="Turing Complete Type Systems"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/turing_complete_type_systems"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title>Turing Complete Type Systems · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link">Turing Complete Type Systems</a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/turing_complete_type_systems.md 25 - ">View Raw</a> 26 - <div class="separator"></div> 27 - <div class="date"> 28 - 17/06 — 2020 29 - <div class="stats"> 30 - <span class="stats-number"> 31 - 9.19 32 - </span> 33 - <span class="stats-unit">cm</span> 34 - &nbsp 35 - <span class="stats-number"> 36 - 0.9 37 - </span> 38 - <span class="stats-unit">min</span> 39 - </div> 40 - </div> 41 - <h1> 42 - Turing Complete Type Systems 43 - </h1> 44 - <div class="post-text"> 45 - <p>Rust’s type system is Turing complete:</p> 46 - <ul> 47 - <li><a href="https://github.com/doctorn/trait-eval/">FizzBuzz with Rust 48 - Traits</a></li> 49 - <li><a href="https://github.com/Ashymad/fortraith">A Forth 50 - implementation with Rust Traits</a></li> 51 - </ul> 52 - <p>It is impossible to determine if a program written in a generally 53 - Turing complete system will ever stop. That is, it is impossible to 54 - write a program <code>f</code> that determines if a program 55 - <code>g</code>, where <code>g</code> is written in a Turing complete 56 - programming language, will ever halt. The <a 57 - href="https://en.wikipedia.org/wiki/Halting_problem">Halting Problem</a> 58 - is in fact, an <a 59 - href="https://en.wikipedia.org/wiki/Undecidable_problem">undecidable 60 - problem</a>.</p> 61 - <p><em>How is any of this relevant?</em></p> 62 - <p>Rust performs compile-time type inference. The type checker, in turn, 63 - compiles and infers types, I would describe it as a compiler inside a 64 - compiler. It is possible that <code>rustc</code> may never finish 65 - compiling your Rust program! I lied, <code>rustc</code> stops after a 66 - while, after hitting the recursion limit.</p> 67 - <p>I understand that this post lacks content.</p> 68 - 69 - </div> 70 - 71 - <div class="intro"> 72 - Hi. 73 - <div class="hot-links"> 74 - <a href="/index.xml" class="feed-button">Subscribe</a> 75 - </div> 76 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 77 - <p>I am currently building <a href="https://tangled.sh">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 78 - <p>Reach out at oppili@libera.chat.</p> 79 - </div> 80 - 81 - <a href="/" class="post-end-link">Home</a> 82 - <span>/</span> 83 - <a href="/posts" class="post-end-link">Posts</a> 84 - <span>/</span> 85 - <a class="post-end-link">Turing Complete Type Systems</a> 86 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/turing_complete_type_systems.md 87 - ">View Raw</a> 88 - </div> 89 - </div> 90 - </body> 91 - </html>
-410
docs/style.css
··· 1 - @import url('https://rsms.me/inter/inter.css'); 2 - @import url(https://cdn.jsdelivr.net/gh/tonsky/FiraCode@3/distr/fira_code.css); 3 - 4 - div.hot-links { 5 - float: right; 6 - padding: 0.3rem 0rem; 7 - margin: -0.5rem 0; 8 - } 9 - 10 - .feed-button, .donate-button { 11 - background-color: var(--dark-white); 12 - border-radius: 0.2rem; 13 - border: 0px solid var(--dark-white); 14 - color: var(--black) !important; 15 - font-size: 0.8rem; 16 - margin: -0.5rem 0; 17 - padding: 0.3rem 0.5rem; 18 - text-decoration: none; 19 - } 20 - 21 - ::-moz-selection, ::selection { 22 - color: var(--black); 23 - background: var(--cyan); 24 - } 25 - 26 - :root { 27 - --cyan: #00cece; 28 - --pink: #d344e2; 29 - --black: #212121; 30 - --light-black: #666666; 31 - --white: #ffffff; 32 - --dark-white: #eeeeee; 33 - --inactive: #888; 34 - 35 - --light-yellow: #F7B955; 36 - --dark-yellow: #F49B0B; 37 - } 38 - 39 - @media (prefers-color-scheme: dark) { 40 - :root { 41 - --cyan: #79ffe1; 42 - --pink: #ff3299; 43 - --black: #dedede; 44 - --light-black: #ccc; 45 - --white: #000; 46 - --dark-white: #323232; 47 - --inactive: #555 48 - } 49 - ::-moz-selection, ::selection { 50 - color: var(--white); 51 - background: var(--cyan); 52 - } 53 - } 54 - 55 - body { 56 - font-family: 'Inter', sans-serif; 57 - font-feature-settings: 'liga' 1, 'calt' 1; /* fix for Chrome */ 58 - } 59 - @supports (font-variation-settings: normal) { 60 - body { 61 - font-family: 'InterVariable', sans-serif; 62 - font-feature-settings: 'ss01' 1, 'kern' 1, 'liga' 1, 'cv05' 1, 'tnum' 1; 63 - } 64 - } 65 - 66 - body { 67 - background-color: var(--white); 68 - color: var(--black); 69 - padding: 0; 70 - padding-top: 2rem; 71 - padding-bottom: 2rem; 72 - text-align: center; 73 - } 74 - 75 - html { 76 - scroll-behavior: smooth; 77 - padding: 0; 78 - line-height: 1.5; 79 - font-size: 18px; 80 - } 81 - 82 - h1 { 83 - font-size: 1.8rem; 84 - } 85 - h2 { 86 - font-size: 1.6rem; 87 - } 88 - h3 { 89 - font-size: 1.4rem; 90 - } 91 - h4 { 92 - font-size: 1.2rem; 93 - } 94 - 95 - h1, h2, h3 { 96 - margin: 0; 97 - margin-top: 3rem; 98 - } 99 - h4, h5, h6 { 100 - margin: 0; 101 - margin-top: 1.5rem; 102 - } 103 - 104 - .recent-heading { 105 - font-size: 0.8rem; 106 - text-transform: uppercase; 107 - color: var(--black); 108 - padding-top: 3rem; 109 - } 110 - 111 - .subheading { 112 - font-weight: 400; 113 - font-size: 0.8rem; 114 - color: var(--light-black); 115 - margin-top: 2rem; 116 - margin-bottom: 2rem; 117 - } 118 - 119 - figcaption { 120 - text-align: center; 121 - } 122 - 123 - table { 124 - width: 100%; 125 - border-collapse: collapse; 126 - } 127 - 128 - .table-post { 129 - width: 70%; 130 - } 131 - 132 - .table-stats { 133 - width: 30%; 134 - text-align: right; 135 - } 136 - 137 - td { 138 - padding: 0.5rem 0; 139 - } 140 - 141 - .posts { 142 - width: 95%; 143 - max-width: 650px; 144 - } 145 - 146 - pre:not(.sourceCode),div.sourceCode { 147 - overflow: auto !important; 148 - padding: 1.3rem; 149 - margin: 1rem -1.3rem !important; 150 - border: 2px solid var(--dark-white); 151 - border-radius: 0.4rem; 152 - } 153 - 154 - pre, code { 155 - font-family: 'Fira Code', monospace; 156 - } 157 - 158 - @supports (font-variation-settings: normal) { 159 - pre, code { 160 - font-family: 'Fira Code VF', monospace; 161 - } 162 - } 163 - 164 - code { 165 - color: var(--pink); 166 - } 167 - 168 - pre > code { 169 - white-space: pre !important; 170 - border: none; 171 - color: var(--black); 172 - } 173 - 174 - /* art stuff */ 175 - #photos { 176 - -webkit-column-count: 2; 177 - -webkit-column-gap: 12px; 178 - -moz-column-count: 2; 179 - -moz-column-gap: 12px; 180 - column-count: 2; 181 - column-gap: 12px; 182 - } 183 - 184 - #photos img { 185 - /* Just in case there are inline attributes */ 186 - width: 100% !important; 187 - height: auto !important; 188 - border: 0px; 189 - border-radius: 0.4rem; 190 - box-shadow: none; 191 - } 192 - 193 - .photo-container { 194 - position: relative; 195 - width: 100%; 196 - margin-bottom: 12px; 197 - } 198 - 199 - .photo-overlay { 200 - line-height: 0; 201 - position: absolute; 202 - top: 0; 203 - bottom: 0; 204 - left: 0; 205 - right: 0; 206 - width: 100% !important; 207 - height: auto !important; 208 - opacity: 0; 209 - transition: .3s ease; 210 - background: linear-gradient(rgba(0, 0, 0, 0.7), rgba(255, 255, 255, 0.0)); 211 - border-radius: 0.4rem; 212 - } 213 - 214 - .photo-text { 215 - line-height: 1; 216 - padding: 12px; 217 - color: white; 218 - } 219 - 220 - .photo-title { 221 - font-size: 1rem; 222 - color: white; 223 - } 224 - 225 - .photo-date { 226 - margin-top: 0.2rem; 227 - font-size: 0.6rem; 228 - color: white; 229 - } 230 - 231 - .photo-container:hover .photo-overlay { 232 - opacity: 1; 233 - } 234 - 235 - /* end of art stuff */ 236 - 237 - @media only screen and (min-width: 1080px) { 238 - .posts { 239 - max-width: 650px; 240 - } 241 - #photos { 242 - -moz-column-count: 2; 243 - -webkit-column-count: 2; 244 - column-count: 2; 245 - } 246 - } 247 - 248 - @media only screen and (min-width: 1400px) { 249 - .posts { 250 - max-width: 650px; 251 - } 252 - #photos { 253 - -moz-column-count: 2; 254 - -webkit-column-count: 2; 255 - column-count: 2; 256 - } 257 - } 258 - 259 - .posts { 260 - text-align: left; 261 - display: inline-block; 262 - } 263 - 264 - 265 - .post { 266 - margin: 0.5rem; 267 - } 268 - 269 - figure { 270 - text-align: center; 271 - } 272 - 273 - img { 274 - max-width: 100%; 275 - border: 2px solid var(--dark-white); 276 - border-radius: 0.4rem; 277 - box-shadow: 0 0 1.5rem 0.5rem rgba(0, 0, 0, 0.10); 278 - } 279 - 280 - video { 281 - max-width: 100%; 282 - border: 2px solid var(--dark-white); 283 - border-radius: 0.4rem; 284 - box-shadow: 0 0 1.5rem 0.5rem rgba(0, 0, 0, 0.10); 285 - } 286 - 287 - hr { 288 - height: 2px; 289 - background-color: var(--dark-white); 290 - border: none; 291 - } 292 - 293 - .date { 294 - font-weight: 400; 295 - font-size: 0.8rem; 296 - color: var(--light-black); 297 - } 298 - 299 - .separator { 300 - margin: 0; 301 - margin-top: 1rem; 302 - margin-bottom: 1rem; 303 - border-top: 2px solid var(--dark-white); 304 - } 305 - 306 - 307 - .intro { 308 - font-size: 0.8rem; 309 - overflow: auto; 310 - padding: 1.3rem; 311 - padding-right: 0.6rem; 312 - border: 2px solid var(--dark-white); 313 - border-radius: 0.4rem; 314 - margin: 0.7rem -1.3rem; 315 - font-family: 'Inter', sans-serif; 316 - } 317 - 318 - .adjusted-asterisk { 319 - position: relative; 320 - padding: 0 0 0 -1rem; 321 - font-size: 8rem; 322 - top: 0.35em; 323 - left: 0.1em; 324 - } 325 - 326 - .heading { 327 - margin-right: 0.5rem; 328 - color: var(--black); 329 - text-align: center; 330 - font-size: 6rem; 331 - text-shadow: 0.10rem 0rem var(--light-black), 0.25rem 0rem var(--cyan), 0.5rem 0rem var(--pink); 332 - 333 - line-height: 5rem; 334 - font-weight: 900; 335 - font-style: italic; 336 - 337 - display:inline-block; 338 - -webkit-transform:scale(1,1.2); 339 - -moz-transform:scale(1,1.2); 340 - -ms-transform:scale(1,1.2); 341 - -o-transform:scale(1,1.2); 342 - transform:scale(1,1.2); 343 - } 344 - 345 - a, a:hover, a:visited, a:active { 346 - transition: ease 0.2s; 347 - color: var(--light-black); 348 - } 349 - 350 - a.post-link, a.post-link:hover, a.post-link:active { 351 - color: var(--black); 352 - font-weight: 600; 353 - font-size: 1.1rem; 354 - text-decoration: none; 355 - } 356 - 357 - a.post-link:visited { 358 - color: var(--inactive); 359 - } 360 - 361 - .post-end-link, .post-end-link:hover, .post-end-link:visited, .post-end-link:active 362 - { 363 - text-decoration: none; 364 - color: var(--light-black); 365 - text-shadow: none; 366 - font-size: 0.8rem; 367 - } 368 - 369 - .post-text { 370 - font-size: 0.9rem; 371 - } 372 - 373 - ul { 374 - list-style: lower-greek inside none; 375 - } 376 - 377 - .stats { 378 - float: right; 379 - text-align: right; 380 - color: var(--light-black); 381 - } 382 - 383 - .stats-number { 384 - font-size: 0.8rem; 385 - } 386 - 387 - .stats-unit { 388 - font-size: 0.6rem; 389 - } 390 - 391 - .footer { 392 - font-size: 0.8rem; 393 - text-align: center; 394 - } 395 - 396 - .footimgs { 397 - max-height: 0.7rem; 398 - display: inline-block; 399 - vertical-align: middle; 400 - image-orientation: from-image; 401 - cursor: pointer; 402 - box-shadow: none; 403 - } 404 - 405 - blockquote { 406 - margin: 0; 407 - padding-left: 0.8rem; 408 - border-left: 2px solid var(--dark-white); 409 - } 410 -
-167
docs/syntax.css
··· 1 - code { 2 - white-space: pre-wrap; 3 - } 4 - 5 - span.smallcaps { 6 - font-variant: small-caps; 7 - } 8 - 9 - span.underline { 10 - text-decoration: underline; 11 - } 12 - 13 - div.column { 14 - display: inline-block; 15 - vertical-align: top; 16 - width: 50%; 17 - } 18 - 19 - div.hanging-indent { 20 - margin-left: 1.5em; 21 - text-indent: -1.5em; 22 - } 23 - 24 - ul.task-list { 25 - list-style: none; 26 - } 27 - 28 - pre > code.sourceCode { 29 - white-space: pre; 30 - position: relative; 31 - } 32 - 33 - pre > code.sourceCode > span { 34 - display: inline-block; 35 - line-height: 1.25; 36 - } 37 - 38 - pre > code.sourceCode > span:empty { 39 - height: 1.2em; 40 - } 41 - 42 - code.sourceCode > span { 43 - color: inherit; 44 - text-decoration: inherit; 45 - } 46 - 47 - div.sourceCode { 48 - margin: 1em 0; 49 - } 50 - 51 - pre.sourceCode { 52 - margin: 0; 53 - } 54 - 55 - @media screen { 56 - div.sourceCode { 57 - overflow: auto; 58 - } 59 - } 60 - 61 - @media print { 62 - pre > code.sourceCode { 63 - white-space: pre-wrap; 64 - } 65 - pre > code.sourceCode > span { 66 - text-indent: -5em; 67 - padding-left: 5em; 68 - } 69 - } 70 - 71 - pre.numberSource code { 72 - counter-reset: source-line 0; 73 - } 74 - 75 - pre.numberSource code > span { 76 - position: relative; 77 - left: -4em; 78 - counter-increment: source-line; 79 - } 80 - 81 - pre.numberSource code > span > a:first-child::before { 82 - content: counter(source-line); 83 - position: relative; 84 - left: -1em; 85 - text-align: right; 86 - vertical-align: baseline; 87 - border: none; 88 - display: inline-block; 89 - -webkit-touch-callout: none; 90 - -webkit-user-select: none; 91 - -khtml-user-select: none; 92 - -moz-user-select: none; 93 - -ms-user-select: none; 94 - user-select: none; 95 - padding: 0 4px; 96 - width: 4em; 97 - } 98 - 99 - pre.numberSource { 100 - margin-left: 3em; 101 - padding-left: 4px; 102 - } 103 - 104 - div.sourceCode { 105 - } 106 - 107 - @media screen { 108 - pre > code.sourceCode > span > a:first-child::before { 109 - text-decoration: underline; 110 - } 111 - } 112 - 113 - code span.al { 114 - font-weight: bold; 115 - } 116 - 117 - code span.an { 118 - font-style: italic; 119 - } 120 - 121 - code span.cf { 122 - font-weight: bold; 123 - } 124 - 125 - code span.co { 126 - font-style: italic; 127 - } 128 - 129 - code span.cv { 130 - font-style: italic; 131 - } 132 - 133 - code span.do { 134 - font-style: italic; 135 - } 136 - 137 - code span.dt { 138 - text-decoration: underline; 139 - } 140 - 141 - code span.er { 142 - font-weight: bold; 143 - } 144 - 145 - code span.in { 146 - font-style: italic; 147 - } 148 - 149 - code span.kw { 150 - font-weight: bold; 151 - } 152 - 153 - code span.pp { 154 - font-weight: bold; 155 - } 156 - 157 - code span.wa { 158 - font-style: italic; 159 - } 160 - 161 - code span.st { 162 - color: var(--cyan); 163 - } 164 - 165 - code span.at { 166 - color: var(--pink); 167 - }
-30
drafts/code_review_diagnostics.md
··· 1 - What if you could view code review comments in your editor 2 - as LSP diagnostics? 3 - 4 - Requirements: 5 - 6 - - The forge to support a format like [f3](https://f3.forgefriends.org/) 7 - - Users can write reviews in the browser 8 - 9 - More technically: 10 - 11 - - The "LSP" would expect the forge to support a format like [f3](https://f3.forgefriends.org/) 12 - - Your editor will sync review comments from your codeforge 13 - - Your LSP should know when to show these diagnostics 14 - * in branch-based review, reviews are not really attached 15 - to anything, but based on current git branch perhaps? 16 - * in commit-based review, reviews can be attached to a 17 - change-id (a la jujutsu), and displayed based on current 18 - working copy change-id 19 - 20 - Cons: 21 - 22 - - How do you reply to these? Is there a way to introduce 23 - bidirectional communication? 24 - - Review comments are not always on code, they could be on 25 - commit message or just general architecture, for which an 26 - "inbox" is a better abstraction 27 - - Bidirectional communication eventually becomes a chat app 28 - 29 - This thought was inspired by recent comments on 30 - "offline-first" reviews by [matklad]()
+32
dune-project
··· 1 + (lang dune 3.21) 2 + 3 + (name site) 4 + 5 + (generate_opam_files true) 6 + 7 + (source 8 + (github username/reponame)) 9 + 10 + (authors "Author Name <author@example.com>") 11 + 12 + (maintainers "Maintainer Name <maintainer@example.com>") 13 + 14 + (license LICENSE) 15 + 16 + (documentation https://url/to/documentation) 17 + 18 + (pin 19 + (package (name tw) (version 1.0.0)) 20 + (url "git+https://github.com/samoht/tw")) 21 + 22 + (pin 23 + (package (name cascade) (version dev)) 24 + (url "git+https://github.com/samoht/cascade.git")) 25 + 26 + (package 27 + (name site) 28 + (synopsis "A short synopsis") 29 + (description "A longer description") 30 + (depends ocaml omd tw yaml calendar) 31 + (tags 32 + ("add topics" "to describe" your project)))
+58
flake.lock
··· 1 + { 2 + "nodes": { 3 + "gitignore": { 4 + "inputs": { 5 + "nixpkgs": "nixpkgs" 6 + }, 7 + "locked": { 8 + "lastModified": 1762808025, 9 + "narHash": "sha256-XmjITeZNMTQXGhhww6ed/Wacy2KzD6svioyCX7pkUu4=", 10 + "owner": "hercules-ci", 11 + "repo": "gitignore.nix", 12 + "rev": "cb5e3fdca1de58ccbc3ef53de65bd372b48f567c", 13 + "type": "github" 14 + }, 15 + "original": { 16 + "owner": "hercules-ci", 17 + "repo": "gitignore.nix", 18 + "type": "github" 19 + } 20 + }, 21 + "nixpkgs": { 22 + "locked": { 23 + "lastModified": 1666603677, 24 + "narHash": "sha256-apAEIj+z1iwMaMJ4tB21r/VTetfGDLDzuhXRHJknIAU=", 25 + "owner": "NixOS", 26 + "repo": "nixpkgs", 27 + "rev": "074da18a72269cc5a6cf444dce42daea5649b2fe", 28 + "type": "github" 29 + }, 30 + "original": { 31 + "id": "nixpkgs", 32 + "type": "indirect" 33 + } 34 + }, 35 + "nixpkgs_2": { 36 + "locked": { 37 + "lastModified": 1776329215, 38 + "narHash": "sha256-a8BYi3mzoJ/AcJP8UldOx8emoPRLeWqALZWu4ZvjPXw=", 39 + "owner": "NixOS", 40 + "repo": "nixpkgs", 41 + "rev": "b86751bc4085f48661017fa226dee99fab6c651b", 42 + "type": "github" 43 + }, 44 + "original": { 45 + "id": "nixpkgs", 46 + "type": "indirect" 47 + } 48 + }, 49 + "root": { 50 + "inputs": { 51 + "gitignore": "gitignore", 52 + "nixpkgs": "nixpkgs_2" 53 + } 54 + } 55 + }, 56 + "root": "root", 57 + "version": 7 58 + }
+38
flake.nix
··· 1 + { 2 + inputs = { 3 + gitignore = { 4 + url = "github:hercules-ci/gitignore.nix"; 5 + }; 6 + }; 7 + 8 + outputs = { 9 + self, 10 + nixpkgs, 11 + gitignore, 12 + }: let 13 + inherit (gitignore.lib) gitignoreSource; 14 + 15 + supportedSystems = ["aarch64-darwin"]; 16 + forAllSystems = nixpkgs.lib.genAttrs supportedSystems; 17 + nixpkgsFor = forAllSystems (system: 18 + import nixpkgs { 19 + inherit system; 20 + }); 21 + in { 22 + formatter = forAllSystems (system: nixpkgsFor."${system}".alejandra); 23 + 24 + devShells = forAllSystems (system: let 25 + pkgs = nixpkgsFor."${system}"; 26 + in { 27 + default = pkgs.mkShell { 28 + packages = with pkgs; [ 29 + pkgs.ocaml 30 + pkgs.opam 31 + pkgs.ocamlPackages.utop 32 + pkgs.ocamlPackages.dune_3 33 + pkgs.ocamlPackages.odoc 34 + ]; 35 + }; 36 + }); 37 + }; 38 + }
-252
generate.sh
··· 1 - #! /usr/bin/env nix-shell 2 - 3 - #! nix-shell -i bash -p eva pandoc esh 4 - 5 - INDEX_BLOG_LIMIT=3 6 - INDEX_BLOG_COUNT=0 7 - INDEX_ART_LIMIT=1 8 - INDEX_ART_COUNT=0 9 - 10 - source ./scripts.sh 11 - 12 - link_wrapper() { 13 - # 1 - id 14 - # 2 - title 15 - # 3 - date 16 - # 4 - read time 17 - echo -ne " 18 - <tr> 19 - <td class="table-post"> 20 - <div class=\"date\"> 21 - $3 22 - </div> 23 - <a href=\"/posts/$1\" class=\"post-link\"> 24 - <span class=\"post-link\">$2</span> 25 - </a> 26 - </td> 27 - <td class="table-stats"> 28 - <span class=\"stats-number\"> 29 - $4 30 - </span> 31 - <span class="stats-unit">min</span> 32 - </td> 33 - </tr> 34 - " 35 - } 36 - 37 - more_links() { 38 - # 1 - sub page 39 - cat << EOF 40 - <tr><td><a href="/$1" class="post-end-link">More ⟶ </a></td></tr> 41 - EOF 42 - } 43 - 44 - recent_link() { 45 - # 1 - sub page 46 - cat << EOF 47 - <tr><td class="recent-heading"><span class="recent-heading">Recent $1</span></td></tr> 48 - EOF 49 - } 50 - 51 - art_block() { 52 - # 1 - filename 53 - # 2 - title 54 - # 3 - date 55 - echo -ne " 56 - <tr> 57 - <td class="table-post"> 58 - <div class=\"date\"> 59 - $3 60 - </div> 61 - <a href=\"/art/$1\" class=\"post-link\"> 62 - <span class=\"post-link\">$2</span> 63 - </a> 64 - </td> 65 - <td class=\"table-stats\"> 66 - <a href=\"/art/$1\"> 67 - <img src=\"/art/$1\" height=\"50px\"> 68 - </a> 69 - </td> 70 - </tr> 71 - " 72 - } 73 - 74 - intro() { 75 - echo -ne " 76 - <div class=\"intro\"> 77 - Hi. 78 - <div class=\"hot-links\"> 79 - <a href=\"/index.xml\" class=\"feed-button\">Subscribe</a> 80 - </div> 81 - <p>I'm Akshay, programmer, pixel-artist & programming-language enthusiast.</p> 82 - <p>I am currently building <a href=\"https://tangled.sh\">tangled.sh</a> — a new social-enabled code-collaboration platform.</p> 83 - <p>Reach out at oppili@libera.chat.</p> 84 - </div> 85 - " 86 - } 87 - 88 - breadcrumbs() { 89 - # 1 - path 90 - cat << EOF 91 - <a href="/" class="post-end-link">Home</a> 92 - <span>/</span> 93 - <a class="post-end-link">$1</a> 94 - EOF 95 - } 96 - 97 - n_logo() { 98 - cat << EOF 99 - <h1 class="heading">oppili</h1> 100 - <h4 class="subheading">previously peppe.rs</h4> 101 - EOF 102 - } 103 - 104 - # setup dirs 105 - 106 - posts=$(ls -t ./posts) 107 - rm -rf "./docs/posts/" 108 - mkdir -p docs/posts 109 - 110 - art=$(ls -t ./art) 111 - rm -rf "./docs/art/" 112 - mkdir -p docs/art 113 - cp -r ./art/* docs/art/ 114 - 115 - 116 - cat << EOF | tee ./docs/posts/index.html ./docs/index.html > /dev/null 117 - <!DOCTYPE html> 118 - <html lang="en"> 119 - <head> 120 - <link rel="stylesheet" href="/style.css"> 121 - <link rel="alternate" type="application/atom+xml" title="oppili's micro musings" href="/index.xml"> 122 - <meta charset="UTF-8"> 123 - <meta name="viewport" content="initial-scale=1"> 124 - <meta content="#ffffff" name="theme-color"> 125 - <meta name="HandheldFriendly" content="true"> 126 - <meta property="og:title" content="oppili's site"> 127 - <meta property="og:type" content="website"> 128 - <meta property="og:description" content="oppili's micro musings"> 129 - <meta property="og:url" content="https://oppi.li"> 130 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 131 - <title>oppi.li</title> 132 - EOF 133 - 134 - # index 135 - echo 136 - echo "[+] INDEX" 137 - echo "$(n_logo)" >> ./docs/index.html 138 - 139 - for p in Posts Art; do 140 - cat << EOF | tee -a ./docs/${p,,}/index.html > /dev/null 141 - <body> 142 - <div class="posts"> 143 - <div class="post"> 144 - $(breadcrumbs $p) 145 - EOF 146 - done 147 - 148 - cat << EOF >> ./docs/index.html 149 - <body> 150 - <div class="posts"> 151 - <div class="post"> 152 - $(intro) 153 - <table> 154 - $(recent_link "Posts") 155 - EOF 156 - 157 - # posts 158 - echo -ne " 159 - <h1>Posts</h1> 160 - <div class=\"separator\"></div> 161 - <table> 162 - " >> ./docs/posts/index.html 163 - 164 - echo 165 - echo "[+] POSTS" 166 - for f in $posts; do 167 - file="./posts/"$f 168 - id="${file##*/}" # ill name my posts just fine 169 - 170 - # generate posts 171 - stats=$(wc "$file") 172 - words="$(echo $stats | awk '{print $2}')" 173 - lines="$(echo $stats | awk '{print $1}')" 174 - 175 - r_time="$(read_time $words)" 176 - height="$(height $lines)" 177 - 178 - post_title=$(title_wrapper "$id") 179 - echo "[~] $post_title" 180 - post_date=$(date -r "$file" "+%d/%m — %Y") 181 - post_link=$(link_wrapper "${id%.*}" "$post_title" "$post_date" "$r_time" "$height") 182 - echo -ne "$post_link" >> ./docs/posts/index.html 183 - 184 - if [[ $INDEX_BLOG_COUNT -lt $INDEX_BLOG_LIMIT ]]; then 185 - echo -ne "$post_link" >> ./docs/index.html 186 - fi 187 - ((INDEX_BLOG_COUNT+=1)) 188 - 189 - id="${id%.*}" 190 - mkdir -p "docs/posts/$id" 191 - esh \ 192 - -o "docs/posts/$id/index.html" \ 193 - post.esh \ 194 - file="$file" \ 195 - id="$id" \ 196 - date="$post_date" \ 197 - title="$post_title" \ 198 - read_time="$r_time" \ 199 - height="$height" \ 200 - intro="$(intro)" 201 - done 202 - 203 - echo "$(more_links posts)" >> ./docs/index.html 204 - echo "$(recent_link Art)" >> ./docs/index.html 205 - 206 - # art 207 - 208 - echo 209 - echo "[+] ART" 210 - 211 - esh \ 212 - -o "docs/art/index.html" \ 213 - art.esh 214 - 215 - for f in $art; do 216 - if [[ $INDEX_ART_COUNT -lt $INDEX_ART_LIMIT ]]; then 217 - file="./art/"$f 218 - id="${file##*/}" 219 - art_title=$(title_wrapper "$id") 220 - art_date=$(date -r "$file" "+%d/%m — %Y") 221 - art_post=$(art_block "$f" "$art_title" "$art_date") 222 - echo "[~] $art_title" 223 - echo -ne "$art_post" >> ./docs/index.html 224 - ((INDEX_ART_COUNT+=1)) 225 - fi 226 - done 227 - 228 - echo "$(more_links art)" >> ./docs/index.html 229 - 230 - # generate rss feeds 231 - echo 232 - echo "[+] Generating RSS feeds ..." 233 - esh \ 234 - -o "./docs/index.xml" \ 235 - "rss.esh" 236 - 237 - cat << EOF | tee -a ./docs/posts/index.html ./docs/index.html > /dev/null 238 - </table> 239 - <div class="separator"></div> 240 - <div class="footer"> 241 - <a href="https://tangled.sh/@oppi.li">Code</a> · 242 - <a href="https://bsky.app/profile/oppi.li">Bluesky</a> · 243 - <a href="mailto:me@oppi.li">Mail</a> 244 - <a href="https://creativecommons.org/licenses/by-nc-sa/4.0/"> 245 - <img class="footimgs" src="https://d33wubrfki0l68.cloudfront.net/94387e9d77fbc8b4360db81e72603ecba3df94a7/632bc/static/cc.svg"> 246 - </a> 247 - </div> 248 - </div> 249 - </div> 250 - </body> 251 - </html> 252 - EOF
+8
lib/breadcrumb.ml
··· 1 + type t = { name : string; href : string } 2 + 3 + let make_with ~name ~href = { name; href } 4 + let make ~name = make_with ~name ~href:("/" ^ name) 5 + let empty ~name = make_with ~name ~href:"#" 6 + let root = make_with ~name:"home" ~href:"/" 7 + let href b = b.href 8 + let name b = b.name
+99
lib/category.ml
··· 1 + module type Named = sig 2 + val name : string 3 + end 4 + 5 + module type Index = sig 6 + type page 7 + 8 + val render : page list -> Tw_html.t 9 + val preview : ?base:string -> limit:int -> page list -> Tw_html.t 10 + end 11 + 12 + module type Cat = sig 13 + type t 14 + 15 + val name : t -> string 16 + val count : t -> int 17 + val build : path:string -> t 18 + val render : t -> Tw_html.t 19 + val preview : t -> limit:int -> Tw_html.t 20 + val css : t -> Tw.Css.t 21 + val write : out_dir:string -> t -> unit 22 + end 23 + 24 + let rec mkdir_p dir = 25 + if Sys.file_exists dir then () 26 + else begin 27 + mkdir_p (Filename.dirname dir); 28 + Unix.mkdir dir 0o755 29 + end 30 + 31 + let write_page ~out_dir path page = 32 + let dir = Filename.concat out_dir path in 33 + let file = Filename.concat dir "index.html" in 34 + mkdir_p (Filename.dirname file); 35 + Out_channel.with_open_text file (fun oc -> 36 + Out_channel.output_string oc (Tw_html.html page)) 37 + 38 + module Default_index (P : Content.Page) : Index with type page = P.t = struct 39 + type page = P.t 40 + 41 + let render posts = 42 + Tw_html.ol 43 + (List.map (fun p -> Tw_html.li [ Tw_html.txt (P.title p) ]) posts) 44 + 45 + let preview ?base:_ ~limit posts = render (List.take limit posts) 46 + end 47 + 48 + module Make (N : Named) (P : Content.Page) (I : Index with type page = P.t) : 49 + Cat = struct 50 + type t = { posts : P.t list; pages : (string * Tw_html.page) list } 51 + 52 + let name _ = N.name 53 + let count t = List.length t.posts 54 + 55 + let build ~path = 56 + let files = Sys.readdir path in 57 + let posts = 58 + Array.to_list files 59 + |> List.filter_map (fun f -> 60 + match P.make ~path:(Filename.concat path f) with 61 + | exception exn -> 62 + Printf.eprintf "[WARN] skipping %s: %s\n%!" f 63 + (Printexc.to_string exn); 64 + None 65 + | p -> Some p) 66 + in 67 + let cat = N.name in 68 + let root = Breadcrumb.root in 69 + let cat_crumb = Breadcrumb.make ~name:cat in 70 + let index = 71 + Layout.page ~title:cat 72 + ~breadcrumbs:[ root; Breadcrumb.empty ~name:cat ] 73 + (I.render posts) 74 + in 75 + let post_pages = 76 + List.map 77 + (fun p -> 78 + let crumbs = 79 + [ root; cat_crumb; Breadcrumb.empty ~name:(P.title p) ] 80 + in 81 + let page = 82 + Layout.page ~title:(P.title p) ~breadcrumbs:crumbs (P.html p) 83 + in 84 + (Filename.concat cat (P.slug p), page)) 85 + posts 86 + in 87 + { posts; pages = (cat, index) :: post_pages } 88 + 89 + let render t = I.render t.posts 90 + let preview t ~limit = I.preview ~base:(N.name ^ "/") ~limit t.posts 91 + 92 + let css t = 93 + Tw.Css.concat 94 + (Typography.stylesheet 95 + :: List.map (fun (_, page) -> snd (Tw_html.css page)) t.pages) 96 + 97 + let write ~out_dir t = 98 + List.iter (fun (path, page) -> write_page ~out_dir path page) t.pages 99 + end
+158
lib/content.ml
··· 1 + type toc_item = { text : string; children : toc_item list } 2 + 3 + module type Page = sig 4 + type t 5 + 6 + val make : path:string -> t 7 + val slug : t -> string 8 + val frontmatter : t -> Yaml.value 9 + val title : t -> string 10 + val date : t -> CalendarLib.Date.t option 11 + val toc : t -> toc_item list 12 + val html : t -> Tw_html.t 13 + end 14 + 15 + module MarkdownPage : Page = struct 16 + type t = { 17 + frontmatter : Yaml.value; 18 + path : string; 19 + body : string; 20 + doc : Omd.doc; 21 + html : Tw_html.t; 22 + } 23 + 24 + let split_frontmatter lines = 25 + match lines with 26 + | "---" :: rest -> 27 + let rec go acc = function 28 + | [] -> failwith "unterminated frontmatter: no closing ---" 29 + | "---" :: body -> (List.rev acc, body) 30 + | l :: rest -> go (l :: acc) rest 31 + in 32 + go [] rest 33 + | _ -> ([], lines) 34 + 35 + let fm_string key fm = 36 + match fm with 37 + | `O fields -> ( 38 + match List.assoc_opt key fields with 39 + | Some (`String s) -> Some s 40 + | _ -> None) 41 + | _ -> None 42 + 43 + let frontmatter t = t.frontmatter 44 + 45 + let title_of_path path = 46 + path |> Filename.basename |> Filename.remove_extension 47 + |> String.map (function '_' -> ' ' | x -> x) 48 + 49 + let title' fm path = 50 + Option.value (fm_string "title" fm) ~default:(title_of_path path) 51 + 52 + let title t = title' t.frontmatter t.path 53 + 54 + let parse_date s = 55 + match String.split_on_char '-' s with 56 + | [ dd; mm; yyyy ] -> ( 57 + try 58 + Some 59 + (CalendarLib.Date.make (int_of_string yyyy) (int_of_string mm) 60 + (int_of_string dd)) 61 + with _ -> None) 62 + | _ -> None 63 + 64 + let date' fm = Option.bind (fm_string "date" fm) parse_date 65 + let date t = date' t.frontmatter 66 + 67 + let rec inline_text = function 68 + | Omd.Concat (_, xs) -> String.concat "" (List.map inline_text xs) 69 + | Omd.Text (_, s) -> s 70 + | _ -> "" 71 + 72 + let rec build_toc min_level = function 73 + | [] -> ([], []) 74 + | (level, _) :: _ as entries when level < min_level -> ([], entries) 75 + | (level, text) :: rest -> 76 + let children, after_children = build_toc (level + 1) rest in 77 + let item = { text; children } in 78 + let siblings, remaining = build_toc min_level after_children in 79 + (item :: siblings, remaining) 80 + 81 + let toc t = 82 + let entries = 83 + Omd.headers t.doc 84 + |> List.map (fun (_, level, inline) -> (level, inline_text inline)) 85 + in 86 + let min_level = List.fold_left (fun acc (l, _) -> min acc l) 6 entries in 87 + if min_level = 6 then [] else fst (build_toc min_level entries) 88 + 89 + let time body = 90 + let words = 91 + String.split_on_char '\n' body 92 + |> List.concat_map (String.split_on_char ' ') 93 + |> List.filter (fun w -> w <> "") 94 + |> List.length 95 + in 96 + max 1 (words / 200) 97 + 98 + let sentences body = 99 + String.fold_left (fun acc c -> if c = '.' then acc + 1 else acc) 0 body 100 + 101 + let height body = 102 + let chars_per_line = 80 in 103 + let line_height_cm = 14.4 *. 0.03528 in 104 + let lines = 105 + String.split_on_char '\n' body 106 + |> List.map (fun line -> 107 + let len = String.length line in 108 + if len = 0 then 1 else (len + chars_per_line - 1) / chars_per_line) 109 + |> List.fold_left ( + ) 0 110 + in 111 + float_of_int lines *. line_height_cm 112 + 113 + let make ~path = 114 + let lines = In_channel.with_open_text path In_channel.input_lines in 115 + let fm_lines, body_lines = split_frontmatter lines in 116 + let frontmatter = Yaml.of_string_exn (String.concat "\n" fm_lines) in 117 + let body = String.concat "\n" body_lines in 118 + let doc = Omd.of_string body in 119 + let title'' = title' frontmatter path in 120 + let mins = time body in 121 + let ht = height body in 122 + let sents = sentences body in 123 + let html = 124 + Tw_html.( 125 + div 126 + ~tw:Tw.[ max_w_xl; mx_auto; text_pretty; mb 12 ] 127 + [ 128 + h1 ~tw:Tw.[ text_center ] [ txt title'' ]; 129 + div 130 + ~tw:Tw.[ text_center; space_x 2. ] 131 + [ 132 + span [ txt (Printf.sprintf "%d min" mins) ]; 133 + span [ txt "\u{00B7}" ]; 134 + span [ txt (Printf.sprintf "%d sentences" sents) ]; 135 + span [ txt "\u{00B7}" ]; 136 + span [ txt (Printf.sprintf "%0.2f cm" ht) ]; 137 + span [ txt "\u{00B7}" ]; 138 + span 139 + [ 140 + txt 141 + (Option.map 142 + (CalendarLib.Printer.Date.sprint "%d.%m.%Y") 143 + (date' frontmatter) 144 + |> Option.value ~default:""); 145 + ]; 146 + ]; 147 + raw (Omd.to_html doc); 148 + ]) 149 + in 150 + { frontmatter; path; body; doc; html } 151 + 152 + let slug t = 153 + Option.value 154 + (fm_string "slug" t.frontmatter) 155 + ~default:(t.path |> Filename.basename |> Filename.remove_extension) 156 + 157 + let html t = t.html 158 + end
+3
lib/dune
··· 1 + (library 2 + (name site) 3 + (libraries yaml tw tw.html omd unix calendar))
+23
lib/layout.ml
··· 1 + let extra_head = 2 + Tw_html.rawf [ {|<style>html { font-size-adjust: ex-height 0.53; }</style>|} ] 3 + 4 + let nav crumbs = 5 + let nodes = 6 + List.mapi 7 + (fun i crumb -> 8 + let node = 9 + Tw_html.( 10 + a 11 + ~at:At.[ href (Breadcrumb.href crumb) ] 12 + [ txt (Breadcrumb.name crumb) ]) 13 + in 14 + if i = 0 then node else Tw_html.(span [ txt " :: "; node ])) 15 + crumbs 16 + in 17 + Tw_html.nav ~tw:Tw.[ max_w_xl; mx_auto ] nodes 18 + 19 + let page ?title ?(breadcrumbs = []) content = 20 + Tw_html.page ?title ~tw_css:"/style.css" [ extra_head ] 21 + [ 22 + Tw_html.div [ nav breadcrumbs; Tw_html.main ~tw:Tw.[ px 2 ] [ content ] ]; 23 + ]
+55
lib/typography.ml
··· 1 + open Tw.Css 2 + open Tw.Css.Selector 3 + 4 + let stylesheet = 5 + v 6 + [ 7 + rule ~selector:(element "html") 8 + [ font_family Serif; line_height (Num 1.2) ]; 9 + rule 10 + ~selector: 11 + (list [ element "h1"; element "h2"; element "h3"; element "h4" ]) 12 + [ margin_top Zero; line_height (Num 1.2); font_weight Bold ]; 13 + rule ~selector:(element "h1") 14 + [ font_size (Rem 1.8); margin_top (Rem 3.0) ]; 15 + rule ~selector:(element "h2") 16 + [ font_size (Rem 1.5); margin_top (Rem 2.5) ]; 17 + rule ~selector:(element "h3") 18 + [ font_size (Rem 1.25); margin_top (Rem 2.0) ]; 19 + rule ~selector:(element "h4") 20 + [ font_size (Rem 1.1); margin_top (Rem 1.5) ]; 21 + rule ~selector:(element "p") [ margin_top (Rem 1.0) ]; 22 + rule ~selector:(element "ul") 23 + [ margin_top (Rem 1.0); padding_left (Rem 1.5); list_style_type Disc ]; 24 + rule ~selector:(element "ol") 25 + [ 26 + margin_top (Rem 1.0); padding_left (Rem 1.5); list_style_type Decimal; 27 + ]; 28 + rule ~selector:(element "li") [ margin_top (Rem 0.25) ]; 29 + rule ~selector:(element "blockquote") 30 + [ 31 + margin_top (Rem 1.5); margin_bottom (Rem 1.5); padding_left (Rem 1.0); 32 + ]; 33 + rule ~selector:(element "pre") 34 + [ 35 + margin_top (Rem 1.5); 36 + margin_bottom (Rem 1.5); 37 + padding [ Rem 0.5 ]; 38 + border ~width:(Px 1.0) ~style:Solid (); 39 + overflow_x Auto; 40 + ]; 41 + rule ~selector:(element "img") [ border ~width:(Px 1.0) ~style:Solid () ]; 42 + rule ~selector:(element "a") 43 + [ color Revert; text_decoration_line Underline ]; 44 + rule ~selector:(element "hr") 45 + [ margin_top (Rem 2.0); margin_bottom (Rem 2.0) ]; 46 + media ~condition:(Media.Prefers_color_scheme `Dark) 47 + [ 48 + rule ~selector:(element "html") 49 + [ background_color (hex "#000000"); color (hex "#d0d0d0") ]; 50 + rule ~selector:(element "a") [ color (hex "#7eb3f8") ]; 51 + rule 52 + ~selector:(compound [ element "a"; Visited ]) 53 + [ color (hex "#c084fc") ]; 54 + ]; 55 + ]
-56
post.esh
··· 1 - <!DOCTYPE html> 2 - <html lang="en"> 3 - <head> 4 - <link rel="stylesheet" href="/style.css"> 5 - <link rel="stylesheet" href="/syntax.css"> 6 - <meta charset="UTF-8"> 7 - <meta name="viewport" content="initial-scale=1"> 8 - <meta content="#ffffff" name="theme-color"> 9 - <meta name="HandheldFriendly" content="true"> 10 - <meta property="og:title" content="<%=$title%>"> 11 - <meta property="og:type" content="website"> 12 - <meta property="og:description" content="a static site {for, by, about} me "> 13 - <meta property="og:url" content="https://oppi.li/posts/<%=$id%>"> 14 - <link rel="icon" type="image/x-icon" href="/favicon.png"> 15 - <title><%=$title%> · oppi.li</title> 16 - <body> 17 - <div class="posts"> 18 - <div class="post"> 19 - <a href="/" class="post-end-link">Home</a> 20 - <span>/</span> 21 - <a href="/posts" class="post-end-link">Posts</a> 22 - <span>/</span> 23 - <a class="post-end-link"><%=$title%></a> 24 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/<% basename $file%>">View Raw</a> 25 - <div class="separator"></div> 26 - <div class="date"> 27 - <%="$date"%> 28 - <div class="stats"> 29 - <span class="stats-number"> 30 - <%="$height"%> 31 - </span> 32 - <span class="stats-unit">cm</span> 33 - &nbsp 34 - <span class="stats-number"> 35 - <%="$read_time"%> 36 - </span> 37 - <span class="stats-unit">min</span> 38 - </div> 39 - </div> 40 - <h1> 41 - <%=$title%> 42 - </h1> 43 - <div class="post-text"> 44 - <% pandoc --quiet -t html --highlight-style monochrome "$file" %> 45 - </div> 46 - <%= "$intro" %> 47 - <a href="/" class="post-end-link">Home</a> 48 - <span>/</span> 49 - <a href="/posts" class="post-end-link">Posts</a> 50 - <span>/</span> 51 - <a class="post-end-link"><%=$title%></a> 52 - <a class="stats post-end-link" href="https://tangled.sh/@oppi.li/site/raw/main/posts/<% basename $file%>">View Raw</a> 53 - </div> 54 - </div> 55 - </body> 56 - </html>
+3
posts/OSC-52.md content/posts/OSC-52.md
··· 1 + --- 2 + date: 27-11-2024 3 + --- 1 4 I use `ssh` a lot. Copying text from the remote machine to 2 5 the host machine always sucked. But OSC-52 makes that easy. 3 6
+22 -19
posts/SDL2_devlog.md content/posts/SDL2_devlog.md
··· 1 + --- 2 + date: 11-04-2021 3 + --- 1 4 I have been working on an editor for the [One Bit 2 5 Image](https://git.peppe.rs/graphics/obi/about) file format 3 6 in Rust and SDL2. This entry in my blog follows my progress ··· 13 16 example, a procedure to draw horizontal black and white 14 17 lines: 15 18 16 - ![Day 20](https://u.peppe.rs/frU.mp4) 19 + ![Day 20](https://cdn.oppi.li/frU.mp4) 17 20 18 21 ### Day 19 19 22 ··· 28 31 drawing procedures too, dealing with floating points is a 29 32 pain. 30 33 31 - ![Day 19](https://u.peppe.rs/1Kb.png) 34 + ![Day 19](https://cdn.oppi.li/1Kb.png) 32 35 33 36 ### Day 18 34 37 ··· 48 51 instead of pixel), and the right most picture is the 49 52 primitive drawing procedure (draws each pixel): 50 53 51 - ![Day 18](https://u.peppe.rs/U4B.png) 54 + ![Day 18](https://cdn.oppi.li/U4B.png) 52 55 53 56 54 57 ### Day 17 ··· 79 82 80 83 The result (brush preview on the bottom right): 81 84 82 - ![Day 17](https://u.peppe.rs/OtU.mp4) 85 + ![Day 17](https://cdn.oppi.li/OtU.mp4) 83 86 84 87 85 88 ### Day 16 ··· 109 112 The following script draws a straight line along a given 110 113 axis, at a given distance from the canvas boundary: 111 114 112 - ![Day 16](https://u.peppe.rs/b3i.mp4) 115 + ![Day 16](https://cdn.oppi.li/b3i.mp4) 113 116 114 117 ### Day 15 115 118 ··· 137 140 ahead and reimplemented brushes, and added a new flood fill 138 141 brush too: 139 142 140 - ![Day 14](https://u.peppe.rs/8q.mp4) 143 + ![Day 14](https://cdn.oppi.li/8q.mp4) 141 144 142 145 143 146 ### Day 13 ··· 149 152 SDL side requires some UX tweaks; environment based 150 153 completion, readline motions sound doable. 151 154 152 - ![Day 13](https://u.peppe.rs/u3.mp4) 155 + ![Day 13](https://cdn.oppi.li/u3.mp4) 153 156 154 157 ### Day 12 155 158 ··· 165 168 their own brushes, dithering patterns, keybinds and more 166 169 (hopefully). 167 170 168 - ![Day 12](https://u.peppe.rs/y0.mp4) 171 + ![Day 12](https://cdn.oppi.li/y0.mp4) 169 172 170 173 ### Day 11 171 174 ··· 174 177 text box widget from scratch, with history and readline like 175 178 editing: 176 179 177 - ![Day 11](https://u.peppe.rs/Mh.mp4) 180 + ![Day 11](https://cdn.oppi.li/Mh.mp4) 178 181 179 182 180 183 ### Day 10 ··· 196 199 be divided into 4x4 grids. Each grid is colored based on the 197 200 intensity of the brush passing over it: 198 201 199 - ![Day 10](https://u.peppe.rs/Mn.png) 202 + ![Day 10](https://cdn.oppi.li/Mn.png) 200 203 201 204 202 205 ### Day 9 ··· 209 212 symmetry, `|` for vertical symmetry, `+` for radial 210 213 symmetry. 211 214 212 - ![Day 9](https://u.peppe.rs/hx.png) 215 + ![Day 9](https://cdn.oppi.li/hx.png) 213 216 214 217 ### Day 8 215 218 ··· 228 231 } 229 232 ``` 230 233 231 - ![Day 8](https://u.peppe.rs/B1.mp4) 234 + ![Day 8](https://cdn.oppi.li/B1.mp4) 232 235 233 236 ### Day 7 234 237 ··· 239 242 the brush size feature. Creating the right abstractions, one 240 243 at a time :) 241 244 242 - ![Day 7](https://u.peppe.rs/xt.mp4) 245 + ![Day 7](https://cdn.oppi.li/xt.mp4) 243 246 244 247 245 248 ### Day 6 ··· 250 253 at once, these points are mirrored over the dividing axis to 251 254 generate the other two quadrants. 252 255 253 - ![Day 6](https://u.peppe.rs/f3.png) 256 + ![Day 6](https://cdn.oppi.li/f3.png) 254 257 255 258 ### Day 5 256 259 ··· 265 268 decided by the application (1-bit in my case). I could 266 269 potentially extend the application to a 24-bit image editor! 267 270 268 - ![Day 5](https://u.peppe.rs/Kh.mp4) 271 + ![Day 5](https://cdn.oppi.li/Kh.mp4) 269 272 270 273 271 274 ### Day 4 ··· 277 280 these operations! I expect abstracting this component will 278 281 come in handy down the line. 279 282 280 - ![Day 4](https://u.peppe.rs/w5.mp4) 283 + ![Day 4](https://cdn.oppi.li/w5.mp4) 281 284 282 285 283 286 ### Day 3 ··· 288 291 to the canvas on right click. I created a make-shift MVC 289 292 architecture à la Elm in Rust. 290 293 291 - ![Day 3](https://u.peppe.rs/GF.mp4) 294 + ![Day 3](https://cdn.oppi.li/GF.mp4) 292 295 293 296 ### Day 2 294 297 ··· 299 302 (measured in unsigned 32 bit integers) is very annoying. 300 303 Hopefully the unchecked conversions won't haunt me later. 301 304 302 - ![Day 2](https://u.peppe.rs/L4.mp4) 305 + ![Day 2](https://cdn.oppi.li/L4.mp4) 303 306 304 307 ### Day 1 305 308 ··· 308 311 allowed me to get all the way to drawing a grid from a 309 312 `Vec<bool>`: 310 313 311 - ![Day 1](https://u.peppe.rs/Ma.png) 314 + ![Day 1](https://cdn.oppi.li/Ma.png)
+3
posts/WPA_woes.md content/posts/WPA_woes.md
··· 1 + --- 2 + date: 12-10-2019 3 + --- 1 4 I finally got around to installing Void GNU/Linux on my main 2 5 computer. Rolling release, non-systemd, need I say more? 3 6
+3
posts/a_reference_counted_afterlife.md content/posts/a_reference_counted_afterlife.md
··· 1 + --- 2 + date: 02-08-2022 3 + --- 1 4 I took interest in the Egyptian rendition of the afterlife 2 5 recently. 3 6
+3
posts/auto-currying_rust_functions.md content/posts/auto-currying_rust_functions.md
··· 1 + --- 2 + date: 08-05-2020 3 + --- 1 4 This post contains a gentle introduction to procedural 2 5 macros in Rust and a guide to writing a procedural macro to 3 6 curry Rust functions. The source code for the entire library
+3
posts/bash_harder_with_vim.md content/posts/bash_harder_with_vim.md
··· 1 + --- 2 + date: 30-07-2019 3 + --- 1 4 Bash is tricky, don't let your editor get in your way. Here's a couple of neat 2 5 additions you could make to your `vimrc` for a better shell programming 3 6 experience.
+3
posts/bye_bye_BDFs.md content/posts/bye_bye_BDFs.md
··· 1 + --- 2 + date: 07-08-2019 3 + --- 1 4 Glyph Bitmap Distribution Format is no more, as the creators of 2 5 [Pango](https://pango.org), one of the most widely used text rendering 3 6 libraries,
+4 -1
posts/call_to_ARMs.md content/posts/call_to_ARMs.md
··· 1 + --- 2 + date: 07-02-2020 3 + --- 1 4 My 4th semester involves ARM programming. And proprietary 2 5 tooling (Keil C). But we don't do that here. 3 6 ··· 76 79 77 80 And it's pretty! See for yourself: 78 81 79 - [![](https://u.peppe.rs/wq.png)](https://u.peppe.rs/wq.png) 82 + [![](https://cdn.oppi.li/wq.png)](https://cdn.oppi.li/wq.png) 80 83 81 84 ### Editing 82 85
+5 -2
posts/color_conundrum.md content/posts/color_conundrum.md
··· 1 + --- 2 + date: 30-12-2019 3 + --- 1 4 This piece aims to highlight (pun intended) some of the 2 5 reasons behind my [color 3 - free](https://u.peppe.rs/bF.png) editor setup. 6 + free](https://cdn.oppi.li/bF.png) editor setup. 4 7 5 8 Imagine highlighting an entire book because *all* of it is 6 9 important. That is exactly what (most) syntax highlighting ··· 21 24 (none, ample and over-the-top highlighting, from left to 22 25 right): 23 26 24 - [![](https://u.peppe.rs/lt.png)](https://u.peppe.rs/lt.png) 27 + [![](https://cdn.oppi.li/lt.png)](https://cdn.oppi.li/lt.png) 25 28 26 29 Without highlighting (far left), it is hard to differentiate 27 30 between comments and code! The florid color scheme (far
+3
posts/configuring_jujutsu.md content/posts/configuring_jujutsu.md
··· 1 + --- 2 + date: 24-05-2025 3 + --- 1 4 There are a lot of reasons to use 2 5 [jujutsu](https://github.com/jj-vcs/jj), but this post is 3 6 not about that. I have this terrible habit of turning every
+3
posts/curing_a_case_of_git-UX.md content/posts/curing_a_case_of_git-UX.md
··· 1 + --- 2 + date: 03-09-2022 3 + --- 1 4 Git worktrees are great, but they fall behind the venerable 2 5 `git checkout` sometimes. I attempted to fix that with 3 6 [fzf](https://github.com/junegunn/fzf) and
+3
posts/font_size_fallacies.md content/posts/font_size_fallacies.md
··· 1 + --- 2 + date: 16-03-2020 3 + --- 1 4 I am not an expert with fonts, but I do have some 2 5 experience [^exp], and common sense. This post aims to debunk some 3 6 misconceptions about font sizes!
+3
posts/get_better_at_yanking_and_putting_in_vim.md content/posts/get_better_at_yanking_and_putting_in_vim.md
··· 1 + --- 2 + date: 30-07-2019 3 + --- 1 4 a couple of nifty tricks to help you copy-paste better: 2 5 3 6 1. reselecting previously selected text (i use this to fix botched selections):
+3
posts/gripes_with_go.md content/posts/gripes_with_go.md
··· 1 + --- 2 + date: 01-08-2020 3 + --- 1 4 You've read a lot of posts about the shortcomings of the Go 2 5 programming language, so what's one more. 3 6
+3
posts/hold_position!.md content/posts/hold_position!.md
··· 1 + --- 2 + date: 30-07-2019 3 + --- 1 4 Often times, when I run a vim command that makes "big" changes to a file (a 2 5 macro or a `:vimgrep` command) I lose my original position and feel disoriented. 3 6
+3
posts/introducing_tablespoon.md content/posts/introducing_tablespoon.md
··· 1 + --- 2 + date: 01-08-2024 3 + --- 1 4 [tbsp](https://git.peppe.rs/languages/tbsp) (tree-based 2 5 source-processing language) is an awk-like language that 3 6 operates on tree-sitter syntax trees. To motivate the need
+3
posts/lightweight_linting.md content/posts/lightweight_linting.md
··· 1 + --- 2 + date: 26-01-2022 3 + --- 1 4 [Tree-sitter](https://tree-sitter.github.io/tree-sitter/using-parsers#pattern-matching-with-queries) 2 5 queries allow you to search for patterns in syntax trees, 3 6 much like a regex would, in text. Combine that with some Rust
+5 -2
posts/lotus58.md content/posts/lotus58.md
··· 1 + --- 2 + date: 13-06-2022 3 + --- 1 4 Earlier this month, I decided that I would laugh at Indian 2 5 customs in the face by building a split-ergo mechanical 3 6 keyboard from scratch rather than purchasing a Moonlander. 4 7 5 - ![The finished product](https://u.peppe.rs/i8k.jpg) 8 + ![The finished product](https://cdn.oppi.li/i8k.jpg) 6 9 7 10 ## Sourcing the parts 8 11 ··· 132 135 133 136 I have been bitten by the ergomech bug. 134 137 135 - ![The Lotus58 in action](https://u.peppe.rs/XM3.jpg) 138 + ![The Lotus58 in action](https://cdn.oppi.li/XM3.jpg)
+3
posts/mounting_the_atmosphere.md content/posts/mounting_the_atmosphere.md
··· 1 + --- 2 + date: 31-07-2025 3 + --- 1 4 [pdsfs](https://tangled.sh/@oppi.li/pdsfs) is a tool that 2 5 mounts [atproto](https://atproto.com) PDS repositories as a 3 6 [FUSE](https://en.wikipedia.org/wiki/Filesystem_in_Userspace)
+4 -1
posts/my_setup.md content/posts/my_setup.md
··· 1 + --- 2 + date: 06-11-2019 3 + --- 1 4 Decided to do one of these because everyone does one of 2 5 these. 3 6 4 - ![](https://u.peppe.rs/Hb.png) 7 + ![](https://cdn.oppi.li/Hb.png) 5 8 6 9 My entire setup is managed with GNU `stow`, making it easier 7 10 to replicate on fresh installations. You can find my
+4 -1
posts/nixOS.md content/posts/nixOS.md
··· 1 + --- 2 + date: 01-09-2020 3 + --- 1 4 I have been eyeing operating systems with functional package 2 5 managers for a while now, aka, NixOS or Guix. Reproducible 3 6 builds, declarative and rollback-able system configuration, ··· 91 94 reinstall NixOS, I can generate an [image of my current 92 95 system](https://github.com/nix-community/nixos-generators). 93 96 94 - [![](https://u.peppe.rs/6m.png)](https://u.peppe.rs/6m.png) 97 + [![](https://cdn.oppi.li/6m.png)](https://cdn.oppi.li/6m.png)
+3
posts/novice_nix:_flake_templates.md content/posts/novice_nix:_flake_templates.md
··· 1 + --- 2 + date: 05-10-2021 3 + --- 1 4 Flakes are very handy to setup entirely pure, 2 5 project-specific dependencies (not just dependencies, but 3 6 build steps, shell environments and more) in a declarative
+3
posts/onivim_sucks.md content/posts/onivim_sucks.md
··· 1 + --- 2 + date: 02-08-2019 3 + --- 1 4 [Onivim](https://v2.onivim.io) is a 'modern modal editor', combining fancy 2 5 interface and language features with vim-style modal editing. What's wrong you 3 6 ask?
+14 -11
posts/pixel_art_in_GIMP.md content/posts/pixel_art_in_GIMP.md
··· 1 + --- 2 + date: 08-04-2020 3 + --- 1 4 I've always been an admirer of pixel art, because of it's 2 5 simplicity and it's resemblance to bitmap font design. 3 6 Recently, I decided to take the dive and make some art of my ··· 14 17 big and you've got too many pixels to work with! 15 18 16 19 I would suggest starting out with anywhere between 100x100 17 - and 200x200. [Here's](https://u.peppe.rs/u9.png) a sample 20 + and 200x200. [Here's](https://cdn.oppi.li/u9.png) a sample 18 21 configuration. 19 22 20 23 Sometimes I use a 10x10 grid, `View > Show Grid` and `Edit > ··· 30 33 Here's a small comparison if you don't know the difference 31 34 between a hard edge and a soft edge: 32 35 33 - ![Hard edge vs Soft Edge](https://u.peppe.rs/kz.png) 36 + ![Hard edge vs Soft Edge](https://cdn.oppi.li/kz.png) 34 37 35 38 I turn the size down all the way to 1 (`[` on the keyboard). 36 - Set `Dynamics` off. [Here's](https://u.peppe.rs/Fs.png) a 39 + Set `Dynamics` off. [Here's](https://cdn.oppi.li/Fs.png) a 37 40 sample brush configuration. 38 41 39 42 ### Laying down the pixels! ··· 62 65 63 66 Your outline might look something like this: 64 67 65 - ![](https://u.peppe.rs/mn.png) 68 + ![](https://cdn.oppi.li/mn.png) 66 69 67 70 Go ahead and fill it in with the fill tool (`Shift + b` on 68 71 the keyboard), add in some seaweed as well, preferably on a 69 72 different layer. You can toggle symmetry on and off to save 70 73 yourself some time. 71 74 72 - ![](https://u.peppe.rs/xu.png) 75 + ![](https://cdn.oppi.li/xu.png) 73 76 74 77 #### Shadows 75 78 ··· 80 83 Shadows on any surface always follow the shape of the 81 84 surface. A spherical onigiri would have a circular shadow: 82 85 83 - ![](https://u.peppe.rs/FU.png) 86 + ![](https://cdn.oppi.li/FU.png) 84 87 85 88 A couple of noticeable changes: 86 89 ··· 95 98 filling in that area with another, darker shadow! An image 96 99 might explain better: 97 100 98 - ![](https://u.peppe.rs/Br.png) 101 + ![](https://cdn.oppi.li/Br.png) 99 102 100 103 To emulate soft lights, reduce the value by 2 to 3 points 101 104 every iteration. Notice how area `1` is much larger than ··· 108 111 flatter object, doesn't cast much of a shadow, so stop with 109 112 1 or 2 iterations of the gradient: 110 113 111 - ![](https://u.peppe.rs/T3.png) 114 + ![](https://cdn.oppi.li/T3.png) 112 115 113 116 We're getting there! 114 117 ··· 121 124 promote the idea of rice grains. Here is the finished 122 125 result: 123 126 124 - ![](https://u.peppe.rs/VE.png) 127 + ![](https://cdn.oppi.li/VE.png) 125 128 126 129 ### Finishing Touches 127 130 128 131 Some color correction and `a e s t h e t i c` Japanese text 129 132 later, our piece is complete! 130 133 131 - ![](https://u.peppe.rs/cn.png) 134 + ![](https://cdn.oppi.li/cn.png) 132 135 133 136 Hold on, why is it so tiny? Well, that's because our canvas 134 137 was 100x100, head over to `Image > Scale Image`, set 135 138 `Quality > Interpolation` to `None` and scale it up to 136 139 700x700, et voilà! 137 140 138 - ![](https://u.peppe.rs/CH.png) 141 + ![](https://cdn.oppi.li/CH.png) 139 142
+5 -2
posts/plain_text_journaling.md content/posts/plain_text_journaling.md
··· 1 + --- 2 + date: 18-06-2023 3 + --- 1 4 I cobbled together a journaling system with {neo,}vim, 2 5 coreutils and [dateutils](http://www.fresse.org/dateutils). 3 6 This system is loosely based on [Ryder 4 7 Caroll's](https://www.rydercarroll.com/) Bullet Journal 5 8 method. 6 9 7 - [![](https://u.peppe.rs/SpF.png)](https://u.peppe.rs/SpF.png) 10 + [![](https://cdn.oppi.li/SpF.png)](https://cdn.oppi.li/SpF.png) 8 11 9 12 ### The format 10 13 ··· 344 347 signifiers, or louder syntax highlighting. Don't expect 345 348 forgiveness from org-mode users though. 346 349 347 - [![](https://u.peppe.rs/ZCK.png)](https://u.peppe.rs/ZCK.png) 350 + [![](https://cdn.oppi.li/ZCK.png)](https://cdn.oppi.li/ZCK.png)
+10 -7
posts/programming_on_34_keys.md content/posts/programming_on_34_keys.md
··· 1 + --- 2 + date: 28-08-2022 3 + --- 1 4 Minimizing your keyboard layout is a slippery slope. A few 2 5 months ago, I built the 3 6 [Ferricy](https://github.com/icyphox/ferricy), a ··· 5 8 the [Ferris Sweep MX 6 9 Bling](https://github.com/davidphilipbarr/Sweep/tree/main/Sweep%20Bling%20MX). 7 10 8 - ![The Ferricy, designed by [icyphox](https://icyphox.sh)](https://u.peppe.rs/otz.jpg) 11 + ![The Ferricy, designed by [icyphox](https://icyphox.sh)](https://cdn.oppi.li/otz.jpg) 9 12 10 13 11 14 My daily use consists of a bit of prose and a lot of ··· 13 16 14 17 # Base Layer 15 18 16 - ![Colemak with no mods](https://u.peppe.rs/base.png) 19 + ![Colemak with no mods](https://cdn.oppi.li/base.png) 17 20 18 21 The base layer contains alphabets, four symbols and four 19 22 whitespace keys: ··· 44 47 As the name suggests, this layer is focused on navigation. 45 48 Arrow keys and the likes. 46 49 47 - ![`NAV`, on holding `space`](https://u.peppe.rs/nav.png) 50 + ![`NAV`, on holding `space`](https://cdn.oppi.li/nav.png) 48 51 49 52 Using Vim and Colemak means you lose out on HJKL navigation. 50 53 However, on activating the `NAV` layer, the right home-row is ··· 62 65 63 66 ## The `SYM` Layer 64 67 65 - ![`SYM`, on holding `enter`](https://u.peppe.rs/sym.png) 68 + ![`SYM`, on holding `enter`](https://cdn.oppi.li/sym.png) 66 69 67 70 This layer contains all the symbols that you would find by 68 71 hitting `Shift` and a key on the number row. Probably ··· 75 78 76 79 ## The `NUM` layer 77 80 78 - ![`NUM`, on holding `tab`](https://u.peppe.rs/num.png) 81 + ![`NUM`, on holding `tab`](https://cdn.oppi.li/num.png) 79 82 80 83 Another deviation from Miryoku, the numpad just feels _right_ 81 84 on my _right_ hand. ··· 91 94 have combos set up for underscore, minus, escape and 92 95 caps-word (more on caps-word later): 93 96 94 - ![Combos are almost piano-like](https://u.peppe.rs/combos.png) 97 + ![Combos are almost piano-like](https://cdn.oppi.li/combos.png) 95 98 96 99 # Home-row Mods 97 100 ··· 102 105 the details. 103 106 104 107 ![Super, Alt, Shift, Ctrl, Hyper; on the left half, and 105 - mirrored on the right half](https://u.peppe.rs/homerow.png) 108 + mirrored on the right half](https://cdn.oppi.li/homerow.png) 106 109 107 110 `Hyper` bridges the gap between firmware and software. You 108 111 can never configure key combination that, opens Firefox, for
+3
posts/rapid_refactoring_with_vim.md content/posts/rapid_refactoring_with_vim.md
··· 1 + --- 2 + date: 31-03-2020 3 + --- 1 4 Last weekend, I was tasked with refactoring the 96 unit 2 5 tests on 3 6 [ruma-events](https://github.com/ruma/ruma-events/pull/70)
+3
posts/self-hosting_git.md content/posts/self-hosting_git.md
··· 1 + --- 2 + date: 17-10-2020 3 + --- 1 4 Earlier this week, I began migrating my repositories from 2 5 Github to [cgit](https://git.zx2c4.com/cgit/about/). If you care at 3 6 all about big corporates turning open-source into a T-shirt
+3
posts/snip_snap.md content/posts/snip_snap.md
··· 1 + --- 2 + date: 29-05-2024 3 + --- 1 4 I regularly switch between exactly two things while working, 2 5 a "current" and an "alternate" item; a lot of tools I use 3 6 seem to support this flow.
+4 -1
posts/static_sites_with_bash.md content/posts/static_sites_with_bash.md
··· 1 + --- 2 + date: 22-11-2019 3 + --- 1 4 After going through a bunch of static site generators 2 5 ([pelican](https://blog.getpelican.com/), 3 6 [hugo](https://gohugo.io), ··· 41 44 42 45 # copy to my file host 43 46 scp -P 443 "$1" emerald:files/"$id" 44 - echo "https://u.peppe.rs/$id" 47 + echo "https://cdn.oppi.li/$id" 45 48 ``` 46 49 47 50 ### Templating
+3
posts/tales_from_mainframe_modernization.md content/posts/tales_from_mainframe_modernization.md
··· 1 + --- 2 + date: 21-05-2025 3 + --- 1 4 At my last workplace, I wrote transpilers (or just 2 5 [compilers](https://people.csail.mit.edu/rachit/post/transpiler/) 3 6 if you prefer) from mainframe languages (COBOL, JCL, BASIC etc.) to
+4 -1
posts/termux_tandem.md content/posts/termux_tandem.md
··· 1 + --- 2 + date: 07-03-2020 3 + --- 1 4 I learnt about `termux` from a friend on IRC recently. 2 5 It looked super gimmicky to me at first, but it eventually 3 6 proved to be useful. Here's what I use it for: ··· 45 48 did use it a couple times when my default music player was 46 49 acting up. `cmus` is a viable option: 47 50 48 - [![](https://u.peppe.rs/CP.jpg)](https://u.peppe.rs/CP.jpg) 51 + [![](https://cdn.oppi.li/CP.jpg)](https://cdn.oppi.li/CP.jpg)
+3
posts/turing_complete_type_systems.md content/posts/turing_complete_type_systems.md
··· 1 + --- 2 + date: 17-06-2020 3 + --- 1 4 Rust's type system is Turing complete: 2 5 3 6 - [FizzBuzz with Rust Traits](https://github.com/doctorn/trait-eval/)
+1 -15
readme.txt
··· 1 - oppi.li 2 - ------- 3 - 4 - static site, put together with bash, esh [0], pandoc [1], 5 - eva [2]. 6 - 7 - build with: 8 - 9 - ./recover.sh 10 - ./generate.sh 11 - 12 - [0]: https://github.com/jirutka/esh 13 - [1]: https://pandoc.org 14 - [2]: https://git.peppe.rs/cli/eva/about 15 - 1 + dune build bin/main.exe
-51
recover.sh
··· 1 - #! /usr/bin/env nix-shell 2 - #! nix-shell -i bash -p dateutils 3 - LIMIT=5000 4 - read_dom () { 5 - ORIGINAL_IFS=$IFS 6 - IFS=\> 7 - read -d \< ENTITY CONTENT 8 - IFS=$ORIGINAL_IFS 9 - } 10 - 11 - repair_post_dates() { 12 - local count=0 13 - local url=https://peppe.rs/posts/ 14 - while read_dom; do 15 - ((count++)) 16 - if [ "$count" -ge "$LIMIT" ]; then 17 - exit 18 - fi 19 - if [[ $ENTITY = "link" ]] ; then 20 - local _f=${CONTENT##$url} 21 - local f="posts/${_f%%/}.md" 22 - elif [[ $ENTITY = "pubDate" ]] ; then 23 - local d=$CONTENT 24 - repair "$d" "$f" 25 - fi 26 - done 27 - } 28 - 29 - repair_art_dates() { 30 - local f d 31 - while mapfile -t -n 2 a && ((${#a[@]})); do 32 - f=${a[0]} 33 - d=${a[1]} 34 - 35 - f=${f##src=\"/} 36 - d=$(dateconv -i '%d/%m — %Y' "$d") 37 - 38 - repair "$d" "$f" 39 - done 40 - } 41 - 42 - repair() { 43 - echo "[+] repairing $2" 44 - touch -am -d "$1" "$2" 45 - } 46 - 47 - grep -B1 pubDate docs/index.xml | repair_post_dates 48 - grep -Eo \ 49 - -e 'src="/art/.*.png' \ 50 - -e '[0-9]{2}/[0-9]{2}...[0-9]{4}' \ 51 - docs/art/index.html | repair_art_dates
-37
rss.esh
··· 1 - <rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"> 2 - <channel> 3 - <title>oppiliappan's μblog</title> 4 - <link>https://oppi.li</link> 5 - <description>programming, design, software</description> 6 - <atom:link href="https://oppi.li/index.xml" rel="self" type="application/rss+xml" /> 7 - <image> 8 - <title>oppiliappan's μblog</title> 9 - <url>https://cdn.oppi.li/n.png</url> 10 - <link>https://oppi.li</link> 11 - </image> 12 - <language>en-us</language> 13 - <copyright>Creative Commons BY-NC-SA 4.0</copyright> 14 - <% for f in `ls -t ./posts`; do 15 - file="./posts/"$f 16 - post_date=$(date -u -r "$file" "+%a, %d %b %Y %H:%M:00 %z") 17 - html=$(pandoc -t html "$file" | sed -e "s/&/\&amp;/g" -e "s/</\&lt;/g" -e "s/>/\&gt;/g") 18 - id="${file##*/}" 19 - id="${id%.*}" 20 - post_title=$(echo "$id" | sed -E -e "s/\..+$//g" -e "s/_(.)/ \u\1/g" -e "s/^(.)/\u\1/g") 21 - post_link="https://oppi.li/posts/$id/" 22 - 23 - echo "<item>" 24 - 25 - echo "<title>$post_title</title>" 26 - echo "<description>$html</description>" 27 - echo "<link>$post_link</link>" 28 - echo "<pubDate>$post_date</pubDate>" 29 - echo "<guid>$post_link</guid>" 30 - 31 - echo "</item>" 32 - 33 - done 34 - %> 35 - 36 - </channel> 37 - </rss>
-16
scripts.sh
··· 1 - title_wrapper() { 2 - # remove extension 3 - # snake to title case 4 - echo "$1" | sed -E -e "s/\..+$//g" -e "s/_(.)/ \u\1/g" -e "s/^(.)/\u\1/g" 5 - } 6 - 7 - read_time() { 8 - minu="$(eva -f 1 $1/150 | xargs)" 9 - echo "$minu" 10 - } 11 - 12 - height() { 13 - cm="$(eva -f 2 $1*18*0.0222 | xargs)" 14 - echo "$cm" 15 - } 16 -
-10
shell.nix
··· 1 - { pkgs ? import <nixpkgs> {} }: 2 - 3 - with pkgs; 4 - mkShell { 5 - buildInputs = [ miniserve ]; 6 - shellHook = '' 7 - source ~/.bash_prompt 8 - export PS1="$PS1(site) " 9 - ''; 10 - }
+36
site.opam
··· 1 + # This file is generated by dune, edit dune-project instead 2 + opam-version: "2.0" 3 + synopsis: "A short synopsis" 4 + description: "A longer description" 5 + maintainer: ["Maintainer Name <maintainer@example.com>"] 6 + authors: ["Author Name <author@example.com>"] 7 + license: "LICENSE" 8 + tags: ["add topics" "to describe" "your" "project"] 9 + homepage: "https://github.com/username/reponame" 10 + doc: "https://url/to/documentation" 11 + bug-reports: "https://github.com/username/reponame/issues" 12 + depends: [ 13 + "dune" {>= "3.21"} 14 + "ocaml" 15 + "omd" 16 + "tw" 17 + "yaml" 18 + "calendar" 19 + "odoc" {with-doc} 20 + ] 21 + build: [ 22 + ["dune" "subst"] {dev} 23 + [ 24 + "dune" 25 + "build" 26 + "-p" 27 + name 28 + "-j" 29 + jobs 30 + "@install" 31 + "@runtest" {with-test} 32 + "@doc" {with-doc} 33 + ] 34 + ] 35 + dev-repo: "git+https://github.com/username/reponame.git" 36 + x-maintenance-intent: ["(latest)"]
-1
sync.sh
··· 1 1 #! /usr/bin/env bash 2 - ssh ferrn git -C www/nerd/site pull & 3 2 ssh laurel git -C /www/site pull
+3
test/dune
··· 1 + (test 2 + (name test_site) 3 + (libraries site))
test/test_site.ml

This is a binary file and will not be displayed.