<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/assets/feed.xslt"?>
<rss version="2.0"
     xmlns:atom="http://www.w3.org/2005/Atom"
     xmlns:content="http://purl.org/rss/1.0/modules/content/"
     xmlns:wfw="http://wellformedweb.org/CommentAPI/">
<channel>
<title>Web logs of McSinyx</title>
<link>https://cnx.gdn</link>
<atom:link href="https://cnx.gdn/feed.xml" rel="self" type="application/rss+xml"/>
<description>Random write-ups packed with pop culture references</description>
<copyright><![CDATA[🄯 2019–2024 Nguyễn Gia Phong under CC BY-SA 4.0]]></copyright>
<language>en</language>
<generator>Franklin</generator>
<item>
  <title>Lazy Ragù</title>
  <link>https://cnx.gdn/blog/metsrc/index.html</link>
  <guid>https://cnx.gdn/blog/metsrc/index.html</guid>
  <description>Ragù in a slow cooker</description>
  <category>lyf</category><category>recipe</category>
  <pubDate>Thu, 10 Oct 2024 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="lazy_ragù">Lazy Ragù</h1>
<p>Craving &#39;em red meat sauces but too busy?  Behold, a ragù recipe so well optimized it can boil the blood of every pizzaliano within a 35-centimetro radius.<sup id="fnref:steffo">[1]</sup></p>
<h2 id="ingredients">Ingredients</h2>
<p>I like to think my sauce is somewhere between Bologna and Napoli even tho it sure ain&#39;t.  Imma list in the order of importance what I usually use down here, but please adjust according to your local market and preference, to quote chef Jean-Pierre:</p>
<blockquote>
<p>If you don&#39;t have/like it, don&#39;t use it.</p>
</blockquote>
<ol>
<li><p>2 fist-sized <a href="https://en.wiktionary.org/wiki/oignon">onyi</a></p>
</li>
<li><p>1 big carrot</p>
</li>
<li><p>A Gordon-Ramsey dash of cooking oil<sup id="fnref:oil">[2]</sup></p>
</li>
<li><p>600 grammi of mince from any mammal<sup id="fnref:longpork">[3]</sup></p>
</li>
<li><p>3 tomatoes<sup id="fnref:tomato">[4]</sup></p>
</li>
<li><p>Salt and pepper</p>
</li>
<li><p>Half a bottle of drinkable red wine<sup id="fnref:measure">[5]</sup></p>
</li>
<li><p>&#39;bout the same amount of <a href="https://en.wikipedia.org/wiki/Stock">stock</a></p>
</li>
<li><p>A few cloves of garlic</p>
</li>
<li><p>A couple of ribs of celery</p>
</li>
<li><p>Some <a href="https://xkcd.com/282">thyme</a> and basil, preferably fresh</p>
</li>
<li><p>Some chili</p>
</li>
<li><p>Paprika powder<sup id="fnref:color">[6]</sup></p>
</li>
</ol>
<p>These should produce about 2 litri of sauce or 4–6 servings,<sup id="fnref:frozen">[7]</sup> for which some hard cheese&#40;s&#41; and butter are also nice to have.</p>
<p>The following tools are also needed:</p>
<ol>
<li><p>Bowls &#40;for storing the ingredients&#41;</p>
</li>
<li><p>Kitchen sink</p>
</li>
<li><p>Knife and cutting board</p>
</li>
<li><p>Fine grater &#40;like for cheese or citrus jest&#41;</p>
</li>
<li><p><a href="https://www.blender.org">Blender</a></p>
</li>
<li><p>Frying pan</p>
</li>
<li><p>Spatula or ladle</p>
</li>
<li><p>Sauce pan or pot</p>
</li>
<li><p>Slow cooker</p>
</li>
</ol>
<h2 id="base_prepping">Base Prepping</h2>
<p>Peel, wash and finely dice the onyi and caramelize &#39;em in a pan with some oil under low heat.  Stir sparingly, this process takes over half an hour and should start before anything else.<sup id="fnref:onyo">[8]</sup> A wee of salt would help draw out the moisture and accelerate the caramelization, which happens well above the water&#39;s boiling point.</p>
<p>In the meantime, wash the other vegetables, finely grate the carrot, peel and mince the garlic, and dice the celery.  When all done, transfer all to the pan and continue frying until the onyi are soft and lightly browned.</p>
<h2 id="broth_prepping">Broth Prepping</h2>
<p>While waiting for the base veggies to caramelize, reduce the wine in half in a sauce pan to get rid of the alcohol.</p>
<p>Blend the tomatoes with the stock &#40;it&#39;s supposed to be a purée without the peel but ain&#39;t nobody got time for that&#41; and the rest of the spices.  Be conservative with the salt, you can always add more but it&#39;s much less easy to remove.</p>
<h2 id="meat_prepping">Meat Prepping</h2>
<p>Move the vegetables into the slow cooker from the pan and use it<sup id="fnref:pronoun">[9]</sup> to sear the minced meat under medium heat until the bottom side is golden brown &#40;no need to stir&#41;.  Parfry in multiple batches if necessary: if the pan is crowded it&#39;d take much longer to reach the desired temperature for the <a href="https://en.wikipedia.org/wiki/Maillard_reaction">Maillard reaction</a>.  Remember, <em>water is the enemy</em>, so leave it a way to retreat.<sup id="fnref:retreat">[10]</sup></p>
<h2 id="cooking">Cooking</h2>
<p>Scoop the meat into the slow cooker and pour in the reduced wine. Gradually add the tomato smoothie while mixing until the liquids barely covers the solids &#40;add more stock if necessary&#41;.</p>
<p>Turn the cooker on low and cook for 4–8 hours or until the meat is tender.</p>
<h2 id="serving">Serving</h2>
<p>Serve with short pasta or rice.  Grate in a generous amount of hard cheeses and drop in a smol slab of butter<sup id="fnref:butter">[11]</sup> and mix well for extra creaminess. Butter is an emulsion, so turn off the stove before adding it to prevent the butterfat from separating.</p>
<p>Plate with fresh basil and thyme and even moar grated cheese if you have any left.</p>
<h2 id="reflection">Reflection</h2>
<p>The recipe is not that lazy to be handy, it nor differs from a normal ragù enough to be a rage bait, but I spent all that time typing it down so I decided to keep the original title for the clickbait values.</p>
<table class="fndef" id="fndef:steffo">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">I&#39;m sorry, Steffo, but thou can&#39;t stop me.</td>
    </tr>
</table><table class="fndef" id="fndef:oil">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">Vegetable oil, animal fat or even butter, smoke point doesn&#39;t matter.</td>
    </tr>
</table><table class="fndef" id="fndef:longpork">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">Yes, <em>any</em> mammal.</td>
    </tr>
</table><table class="fndef" id="fndef:tomato">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content">If not ripe and soft, add a few spoons of tomato paste.</td>
    </tr>
</table><table class="fndef" id="fndef:measure">
    <tr>
        <td class="fndef-backref">[5]</td>
        <td class="fndef-content">Measure carefully&#33;</td>
    </tr>
</table><table class="fndef" id="fndef:color">
    <tr>
        <td class="fndef-backref">[6]</td>
        <td class="fndef-content">Mostly for the color.</td>
    </tr>
</table><table class="fndef" id="fndef:frozen">
    <tr>
        <td class="fndef-backref">[7]</td>
        <td class="fndef-content">It&#39;ll last 17 years in the freezer, so just make a full pot.</td>
    </tr>
</table><table class="fndef" id="fndef:onyo">
    <tr>
        <td class="fndef-backref">[8]</td>
        <td class="fndef-content">Onyo is always number first&#33;</td>
    </tr>
</table><table class="fndef" id="fndef:pronoun">
    <tr>
        <td class="fndef-backref">[9]</td>
        <td class="fndef-content">The pan, not the slow cooker.</td>
    </tr>
</table><table class="fndef" id="fndef:retreat">
    <tr>
        <td class="fndef-backref">[10]</td>
        <td class="fndef-content">圍師必闕。</td>
    </tr>
</table><table class="fndef" id="fndef:butter">
    <tr>
        <td class="fndef-backref">[11]</td>
        <td class="fndef-content">Butter makes everything butter.</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/metsrc@cnx%3E&Subject=Re: Lazy Ragù">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/metsrc@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/metsrc/comments.xml</wfw:commentRss>
</item>
<item>
  <title>GNU as a Router, the Canonical Way</title>
  <link>https://cnx.gdn/blog/route/index.html</link>
  <guid>https://cnx.gdn/blog/route/index.html</guid>
  <description>How to set up an Ubuntu system as a router</description>
  <category>fun</category><category>recipe</category><category>net</category>
  <pubDate>Sat, 03 Aug 2024 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="gnu_as_a_router_the_canonical_way">GNU as a Router, the Canonical Way</h1>
<p>A while ago I noticed that my ISP leases IPv4 addresses out indefinitely. It was everything I&#39;d ever wanted and I gotta seize it to truly <em>self</em>-host. As an experiment, I started on something cheaper, like a single-board compooter. In 2024, support for general-purpose RISC-V chips began to ripen, so naturally due to FOMO, I bought a board with JH-7110.  Boy, was that a mistake&#33; While the bootloaders&#39; support had been well <a href="https://rvspace.org/en/project/JH7110_Upstream_Plan">upstreamed</a>, certain essential features like PCIe &#40;for NVMe&#41; has yet to reached a mainline Linux release, even worse so on the BSDs.  I ended up flashing the <em>only</em> distribution with official support <a href="https://loa.loang.net/chung/D16T24MXDP3T.3BR1X04I90CGT@guix/t">at the time</a>, Ubuntu.</p>
<p>Funny enough, after over a decade of daily driving <a href="https://www.gnu.org">GNU</a>, twas the first time I installed Ubuntu on a machine of my own.  At the time of writing, the reason for the was more apparent than ever: Canonical had been forcing Snap<sup id="fnref:snap">[1]</sup> down the users&#39; throat, even on the <em>server</em> edition. Thankfully Snap was still managed by APT and twas easy enough to remove prevent it from coming back.  Another annoyance was the lack of manual pages in the minimized installation <em>and</em> that the official way to enable them is through a script that also install other bloats SMFH &#40;the script is quite short and the actually necessary commands can be trivially found, I&#39;d rather they&#39;re documented instead&#41;.</p>
<p>That being said, not everything Ubuntu includes due to NIH is bad. Unity &#40;not the game engine that&#39;s proprietary like Snap server&#41; was loved by many; and this article is basically an appreciation post for some others: <a href="https://netplan.io">Netplan</a> and <a href="https://launchpad.net/ufw">ufw</a>.  Before diving in, lemme finish the story to give you the full context of this setup. The SBC is the VisionFive 2 which is blessed with plenty of IO:</p>
<ul>
<li><p>8 GB of memory</p>
</li>
<li><p>4 USB 3.0 type-A ports</p>
</li>
<li><p>2 RJ45 ports &#40;1 Gb and 100/10 Mb&#41;</p>
</li>
<li><p>1 M.2 slot &#40;I used this as an excuse to buy a larger SSD and put the old 256 GB one here&#41;</p>
</li>
<li><p>1 eMMC slot<sup id="fnref:mmc">[2]</sup> &#40;eMMC are cheap, got one also with 256 GB&#41;</p>
</li>
<li><p>1 TF slot</p>
</li>
<li><p>40 pin GP&#40;and predefined-purpose&#41;IO</p>
</li>
<li><p>Other stuff for interfacing with humen like HDMI, audio jack, etc.</p>
</li>
</ul>
<p>Initially, my plan for the SBC was to host services unlisted on the <a href="https://loang.net">loang network</a>.  Official services were not considered because my home network has no IPv6 and sometimes I&#39;ll like to have most of the bandwidth for meself.  Shortly afterwards, I also purchased a somewhat beefy desktop compooter with even more I/O, especially a bunch of SATA, which are a lot more attractive than connecting hard di&#42;ks via USB.  On the other hand, the SBC barely consume any electricity, well under 10 W with the NVMe drive, a Wi-Fi dongle and a fan connected.  Since it cost virtually nothing to keep it up 24/7, I decided to hand it the following two tasks:</p>
<ul>
<li><p>Reverse proxying services running on more powerful machines in the local network.</p>
</li>
<li><p>Acting as a virtual router between nodes I manage. This is particularly useful for tunneling to my work network and accessing the servers, allowing me to work remotely with low latency.</p>
</li>
</ul>
<p>Setting up the VPN with Wireguard was relatively easy, so I assumed swapping the SBC for the home router couldn&#39;t be too hard.  Once again, I <a href="https://antifandom.com/how-i-met-your-mother/wiki/Knight_Vision">chose poorly</a>, this little project&#39;d costed me so many sleepless nights so I figured I should note down what I learned here in case it can save someone else from the same pain.  <strong>Do not take inspiration from this&#33;</strong></p>
<div class="franklin-toc"><ol><li>Connecting to the Internet</li><li>Local Networking</li><li>Wireless Access Point</li><li>Name Resolution</li></ol></div>
<h2 id="connecting_to_the_internet">Connecting to the Internet</h2>
<p>My landlord handles the contract with the ISP so I don&#39;t know the details of the subscription, but there&#39;s certainly no IPv6 nor any static IPv4 address. Bandwidth to datacenters in the region is approximately 100 Mb/s and the wall socket connects to a Cat 5e cable.  I know about the latter because whatever dumb ass did the last maintenance wired that to another short one dangling from the wall socket<sup id="fnref:futa">[3]</sup>, and after getting stabbed in the eyes for months I finally to open it up and made the socket a proper socket.</p>
<p>It would not make the slightest of a difference but I connect the SBC&#39;s 1 Gb port &#40;identified in Ubuntu as end0&#41; to the Internet and the slower one &#40;end1&#41; to my desktop on the local network. Thankfully no <a href="https://docs.fsfe.org/en/teams/router-freedom-tech-wiki">special setup</a> was needed and here is the entire Netplan configuration to connect to the outside world:</p>
<pre><code class="language-yaml">network:
  ethernets:
    end0:
      dhcp4: true
  renderer: networkd
  version: 2</code></pre>
<h2 id="local_networking">Local Networking</h2>
<p>For simplicity&#39;s sake, I decided to use the same subnet for both Ethernet and Wi-Fi under a bridge br0, where addressing and routing is configured:</p>
<pre><code class="language-yaml">network:
  bridges:
    br0:
      addresses:
        - 192.168.147.254/25
      interfaces:
        - end1
      routes:
        - from: 192.168.147.128/25
          on-link: true
          to: 0.0.0.0/0
          type: nat
          via: 192.168.147.254
  ethernets:
    end1:
      dhcp4: false</code></pre>
<p>As Netplan doesn&#39;t configure any DHCP server, that&#39;s done separately by udhcpd from busybox:</p>
<pre><code class="language-plaintext">interface br0
start 192.168.147.128
end 192.168.147.253
max_leases 126
option subnet 255.255.255.128
option router 192.168.147.254</code></pre>
<p>I couldn&#39;t seem to get a concrete information on the ports used by DHCP so I open the firewall for UDP on both 67 and 68 &#40;I swear this isn&#39;t an engagement bait to test out the new mailing list&#41;:</p>
<pre><code class="language-plaintext">ufw allow in on br0 to any port 67 proto udp
ufw allow in on br0 to any port 68 proto udp</code></pre>
<h2 id="wireless_access_point">Wireless Access Point</h2>
<p>Thanks to systemd, the Wi-Fi dongle is recognized as wlx600dd0g8b33f. Yes, that abomination of a name includes the chip&#39;s full MAC address. That being said, I&#39;d like to stick to the basis of a systemd/Linux distro. Netplan doesn&#39;t support Wi-Fi hotspot with systemd-networkd but NetworkManager, so the interface had thus to be declared as Ethernet:</p>
<pre><code class="language-yaml">network:
  bridges:
    br0:
      interfaces:
        - wlx600dd0g8b33f
  ethernets:
    wlx600dd0g8b33f:
      dhcp4: false</code></pre>
<p>Actual wireless connectivity is handled by hostapd:</p>
<pre><code class="language-ini">interface&#61;wlx600dd0g8b33f
bridge&#61;br0ssid&#61;YΦ
utf8_ssid&#61;1
country_code&#61;KR
channel&#61;6
ieee80211d&#61;1
ieee80211h&#61;1
ieee80211n&#61;1
hw_mode&#61;g
wmm_enabled&#61;1wpa&#61;2
wpa_pairwise&#61;TKIP
wpa_passphrase&#61;just enter random characters</code></pre>
<h2 id="name_resolution">Name Resolution</h2>
<p>My ISP is <a href="https://www.tomshardware.com/tech-industry/cyber-security/south-korean-telecom-company-attacks-torrent-users-with-malware-over-600000-people-report-missing-files-strange-folders-and-disabled-pcs">known to be evil</a> so I&#39;d rather rely on more reputable resolvers like <a href="https://opennic.org">OpenNIC</a>, which also offers free-of-charge &#40;&#33;&#41; domain names. Most of their <a href="https://servers.opennic.org">tier 2</a> servers are located on the other side of the globe &#40;200 to 300 ms RTT&#41;, so a local cache is almost required.  <a href="https://pymumu.github.io/smartdns/en">SmartDNS</a> seems to be the best fit for this purpose, as it queries upstream servers simultaneously and also check for the IP with the lowest RTT among the results. Since I don&#39;t trust my ISP, connections to the upstream servers are encrypted:</p>
<pre><code class="language-plaintext">bind :53@br0
server-tls 51.254.162.59 -host-name ns1-dot.iriseden.fr
server-tls 202.61.197.122 -host-name dns.furrydns.de
server-tls 80.152.203.134 -host-name dot.kekew.info
server-tls 178.201.248.159 -host-name dot.kekew.info
server-tls 178.201.248.160 -host-name dot.kekew.info
server-tls 95.216.99.249 -host-name dns.froth.zone</code></pre>
<p>For the router itself, the nameserver is set in /etc/resolv.conf and Netplan is told not to change it:</p>
<pre><code class="language-yaml">network:
  ethernets:
    end0:
      dhcp4-use-dns: false</code></pre>
<p>After ufw is configured to allow UDP traffic in port 53 on br0, udhcpd is instructed to advertise this local DNS server:</p>
<pre><code class="language-plaintext">option dns 192.168.147.254</code></pre>
<p>I might consider blocking ads at the domain-name level someday, but for now uBlock Origin is working well enough on my systems and I rarely have people over, especially not for looking at <em>their</em> electronic devices.</p>
<table class="fndef" id="fndef:snap">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">Not <a href="http://snap.berkeley.edu">the good one</a>.</td>
    </tr>
</table><table class="fndef" id="fndef:mmc">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">Innovation&#39;s gone full circle, <em>eMMC</em> is short for <em>embedded MMC</em>.</td>
    </tr>
</table><table class="fndef" id="fndef:futa">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">Basically a futanari of the RJ45 world.</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/route@cnx%3E&Subject=Re: GNU as a Router, the Canonical Way">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/route@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/route/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Best Ways to Watch YouTube Videos</title>
  <link>https://cnx.gdn/blog/youtu/index.html</link>
  <guid>https://cnx.gdn/blog/youtu/index.html</guid>
  <description>Do you know de wey?  Lemme show you de wey&#33;</description>
  <category>fun</category><category>recipe</category><category>net</category><category>nix</category><category>clipboard</category>
  <pubDate>Wed, 17 Jan 2024 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="best_ways_to_watch_youtube_videos">Best Ways to Watch YouTube Videos</h1>
<p>In today&#39;s episode of <a href="https://www.alexmolas.com/2023/07/15/nobody-cares-about-your-blog.html">guides nobody asked for and likely having been covered by someone more qualified</a>, lemme show you the <em>correct</em> ways to view videos hosted on YouTube and other hostile, tracker-riddled hellscapes.  Whilst I despise Google&#39;s mass surveillance practices, it stores a large proportion of culturally significant videos and clips that would be difficult to mirror to user-respecting services due to copyright.  Hell, even YouTube doesn&#39;t have the right to distribute many of them in the first place.</p>
<p>Because of YouTube&#39;s circumvention of advertisement blockers, the ad-blocking arm race finally caught mainstream media attention and tis kool to talk about that now.  Hence I&#39;m happy to jump on the bandwagon, albeit a wee bit late, but this ain&#39;t just that. Since I feed you poison—over 4&#37; of the pages linked from my site are on YouTube—the least I can do is sell you my cures.</p>
<h2 id="using_a_proper_media_player">Using a Proper Media Player</h2>
<p>The most popular solutions are either to use for a good blocker on a browser with &#40;supposedly&#41; long-term support for <a href="https://github.com/uBlockOrigin/uBlock-issues/issues/338#issuecomment-1332300191">Manifest V2</a> like <a href="https://ublockorigin.com">uBlock Origin</a> on Firefox, or use alternative front-ends such as <a href="https://invidious.io">Invidious</a> or <a href="https://github.com/TeamPiped/Piped">Piped</a>.  Although uBlock Origin is essential for a pleasant experience on the modern interwebs and alternative frontends offers the best UX for browsing videos, in-browser and service-specific media players are inferior anyway when compared to programs properly designed for a decent playback experience.</p>
<p>My favorite has been <a href="https://mpv.io">mpv</a> for as long as I can remember, as it makes it easy to adjust video brightness/contrast/etc., playback speed, subtitle size and placements, and to overamplify quiet audios.  Out of the box, it integrates with <a href="https://github.com/yt-dlp/yt-dlp">yt-dlp</a>, a time shifter with support for <a href="https://github.com/yt-dlp/yt-dlp/raw/master/supportedsites.md">most online media services</a>. Just drop the URL into an mpv window and <a href="https://www.youtube.com/watch?v&#61;_FNzL5nW_u4">boom</a>, it werks&#33;</p>
<p>Either <a href="https://uno.starshard.studio/notes/9nmgldtdgghu8m2n">drag-and-drop</a> or invoking <code>mpv &#36;url</code> is quite convenient, but not that close to following an anchor, is it?  You&#39;d need to first open mpv or a program launcher<sup id="fnref:launch">[1]</sup>, then drag the URL there, or perhaps copy and paste it for the latter cases. What if you <a href="https://ziglang.org/perf">gotta go fast</a>, aye?  As a <a href="https://video.hardlimit.com/c/morbiwars">hedgehog-maxxer</a> meself, of course I can do better, and here&#39;s how.</p>
<h2 id="with_a_browser_add-on">With a Browser Add-on</h2>
<p>While drafting this article, I noticed that the <em>ff2mpv</em> extension I was using had <a href="https://github.com/woodruffw/ff2mpv/commit/2397193b36e6.patch">technically been non-free</a> for a while.  Albeit I understand and respect the author&#39;s noble intention against violence, I believe discrimination never ends up helping those oppressed due to the power imbalance for the exclusion false-positives to be worth it.</p>
<p>For this reason, I switched to <a href="https://addons.mozilla.org/en-US/firefox/addon/iina-open-in-mpv">Open in mpv</a> and recommend it instead. The usage is practically the same: open context menu at the video URL and select <em>Open this link in mpv</em>.  The internal mechanism is a bit different though, and because it influences the installation process, I will try to briefly explain <a href="https://www.youtube.com/watch?v&#61;1Fl2sMV7Hcc">how it works</a>.</p>
<p>The way <em>Open in mpv</em> works is a bit convoluted.  First, it wraps the specified URL in a <code>mpv</code> scheme.  The new URL starts with <code>mpv://</code> is then passed back to Firefox, which must have been configured to open it in the native program <code>open-in-mpv</code>.  This program parses the URL into the equivalent mpv command and execute it.  If you are not on NixOS, see the <a href="https://github.com/Baldomo/open-in-mpv/raw/master/README.md">extension&#39;s README</a> to set it up yourself.</p>
<p>Otherwise, it can be declared in <a href="https://nixos.org/manual/nixos/stable/options#opt-programs.firefox.policies">configuration.nix&#40;5&#41;</a> as follows. The declarations should be self-explanatory after referencing Firefox&#39;s documentation for <a href="https://mozilla.github.io/policy-templates">policies.json</a>.  If you have trouble finding an extension&#39;s ID and download URL, search for it in <a href="https://gnuzilla.gnu.org/mozzarella">Mozzarella</a>.</p>
<pre><code class="language-nix">&#123; pkgs, ... &#125;:
&#123;
  programs.firefox &#61; &#123;
    enable &#61; true;
    policies &#61; &#123;
      ExtensionSettings.&quot;&#123;d66c8515-1e0d-408f-82ee-2682f2362726&#125;&quot; &#61; &#123;
        default_area &#61; &quot;menupanel&quot;;
        installation_mode &#61; &quot;normal_installed&quot;;
        install_url &#61;
          &quot;https://addons.mozilla.org/firefox&quot;
          &#43; &quot;/downloads/latest/iina-open-in-mpv/latest.xpi&quot;;
      &#125;;
      Handlers.scheme.mpv &#61; &#123;
        action &#61; &quot;useHelperApp&quot;;
        ask &#61; false;
        handlers &#61; &#91; &#123;
          name &#61; &quot;open-in-mpv&quot;;
          path &#61; &quot;&#36;&#123;pkgs.open-in-mpv&#125;/bin/open-in-mpv&quot;;
        &#125; &#93;;
      &#125;;
    &#125;;
  &#125;;
&#125;</code></pre>
<p>Even though Mozzarella is supposed to only show libre add-ons, be aware that the metadata it crawls from <a href="https://addons.mozilla.org">addons.mozzila.org</a> might not always be <a href="https://issues.guix.gnu.org/68361">correct</a>.  Ideally, browser extensions should be packaged in the distribution&#39;s repository, but packaging discipline is not exactly NixOS&#39;s strong suit.  I will probably post an update on how to declare <code>policies.json</code> in Guix once I figure that out.</p>
<h2 id="from_a_feed_reader">From a Feed Reader</h2>
<p>Now we can properly watch videos while browsing the web, but subscribing to YouTube channels on its web interface would require creating an account and subjecting one&#39;s self to more surveillance.  Fortunately, at the time of writing, YouTube still provide Atom <a href="https://en.wikipedia.org/wiki/Web_feed">feeds</a> for syndication. Funny enough, they are advertised on the channel pages as RSS:</p>
<pre><code class="language-html">&lt;link rel&#61;&quot;alternate&quot;
      type&#61;&quot;application/rss&#43;xml&quot;
      title&#61;&quot;RSS&quot;
      href&#61;&quot;https://www.youtube.com/feeds/videos.xml?channel_id&#61;…&quot;&gt;</code></pre>
<p>The referenced feed employ <a href="https://www.rssboard.org/media-rss">Media RSS</a> to communicate the video URL. This extension is widely supported by feed readers, as well as the previously mentioned feed-discovery mechanism.  I use <a href="https://lzone.de/liferea">Liferea</a>, which allows me to directly paste the YouTube channel&#39;s URL<sup id="fnref:ytc">[2]</sup>, and displays each video&#39;s description, thumbnail and enclosed media, e.g.</p>
<p><img src="https://cnx.gdn/assets/liferea-youtube.png" alt="Liferea in action" /></p>
<p>For each MIME type to, enclosures can be configured to be opened by a user-preferred program.  In this case, I set <code>mpv --ytdl-format&#61;b</code> for <code>application/x-shockwave-flash</code> &#40;a reminiscence of a time when browsers needed <a href="https://ruffle.rs">Flash</a> to play videos and animations&#41; for the <em>second</em> best quality to save some bandwidth.  YouTube encodes the highest resolution video separate from the audio, so the best combined format <code>b</code> is one level lower than yt-dlp&#39;s default best video and best audio together.</p>
<h2 id="via_clipboard_integration">Via Clipboard Integration</h2>
<p>People also share videos with me via instant messaging. I find it cumbersome to open the URL in the browser then redirect it to the media player, so the clipboard is used as the bridge instead. To do this, I simply create a key binding to the command below.<sup id="fnref:wl">[3]</sup></p>
<pre><code class="language-sh">mpv --ytdl-format&#61;b &quot;&#36;&#40;xclip -out -selection clipboard&#41;&quot;</code></pre>
<h2 id="musing">Musing</h2>
<p>There, I shared how I do it so <a href="https://cnx.gdn/blog/youtu">you can too</a>&#33;  If they seem needlessly complex, you share my disappointment on the UX evolution of the mainstream web.  I dream of a more semantic web, not necessarily web 3.0, perhaps just more explicitly typed, where e.g. a YouTube URL for embedding would be a <code>video/webm</code> instead of a <code>text/html</code>.</p>
<p>If <code>mailto</code> URIs can launch our email client, and social media pages can bug us to open the post in their own app, why can&#39;t we have interoperable media handling?  Maybe we should, but I&#39;m not sure if we can. <a href="https://www.searchenginejournal.com/youtube-is-showing-ads-on-non-monetized-channels/388674">Greed</a> stands in our way.  Providers force us to use their proprietary <a href="https://pluralistic.net/2023/01/21/potemkin-ai">malware</a> to consume their service.  <a href="https://www.defectivebydesign.org">DRM</a> has become the foundation of media distribution.  Grassroots movements like <a href="https://framasoft.org">Framasoft</a> might never reach mainstream status.</p>
<p>I don&#39;t mean to tell you to give up though, just to direct your energy to where it matters.  Spend less on developing <a href="https://sr.ht/~benbusby/farside">alternative front-ends</a> than on ethical replacements, bridges and inviting people over. We need more <a href="https://sepiasearch.org">videos</a>, more <a href="https://www.funkwhale.audio">music</a>, more <a href="https://castopod.org">podcasts</a>, more <a href="https://en.wikipedia.org/wiki/Open_access">knowledge</a>, better <a href="https://xmpp.org">instant</a> <a href="https://matrix.org">messaging</a>, better <a href="https://seirdy.one/posts/2021/03/10/search-engines-with-own-indexes">search engines</a>, better <a href="https://browser.mt">translations</a>, better <a href="https://www.home-assistant.io">home</a> <a href="https://platypush.tech">automation</a>, and whatnot. Against all odds, maybe things will finally start to improve even for those outside of our bubble.  <a href="https://fe.disroot.org/@mcsinyx/posts/ALaW77HgCSPq4pLxpo">Perchance.</a></p>
<table class="fndef" id="fndef:launch">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">Or a terminal emulator</td>
    </tr>
</table><table class="fndef" id="fndef:ytc">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">Something starting with https://www.youtube.com/@</td>
    </tr>
</table><table class="fndef" id="fndef:wl">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">On <a href="https://wayland.social/@compositor/110768798303454842">Wayland</a>, replace <code>xclip</code> with something equivalent</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/youtu@cnx%3E&Subject=Re: Best Ways to Watch YouTube Videos">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/youtu@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/youtu/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Slow Cooked Pork and Eggs</title>
  <link>https://cnx.gdn/blog/kholow/index.html</link>
  <guid>https://cnx.gdn/blog/kholow/index.html</guid>
  <description>Sino-Vietnamese caramelized pork and eggs, but by a slow cooker</description>
  <category>lyf</category><category>recipe</category>
  <pubDate>Sat, 03 Jun 2023 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="slow_cooked_pork_and_eggs">Slow Cooked Pork and Eggs</h1>
<p><a href="https://en.wikipedia.org/wiki/Caramelized_pork_and_eggs">Thịt kho tàu</a>, literally <em>Chinese braised pork</em>, is one of the most common Vietnamese dish, to be found anywhere from military camps<sup id="fnref:USTH">[1]</sup> to fancy restaurants, anytime from family diners to new year holidays. While originated from southeastern China, over the years it adopted local ingredients such as <a href="https://en.wikipedia.org/wiki/Fish_sauce">fish sauce</a> and coconut flesh and probably does not taste the same.</p>
<p>Due to time constraints, home cooks usually relies on fattier cuts such as the belly to maintain the juiciness.  The downside is that the excess fat can quickly tell the liver to tune down the appetite after a few meals.<sup id="fnref:ngán">[2]</sup>  This put me in an awkward position, since I was conditioned to feel wrong about braising a serving size of anything &#40;I was living alone when typing this&#41;.</p>
<p>Though, as said three sentences ago, leaner cuts can be as tender when cooked longer.  This is where slow cookers come to the rescue: they maintain temperature between 80 and 100°C and after eight hours even the toughest cuts will just fall apart.  The best part? No supervision needed.  Water doesn&#39;t even boil at that temperature, so accidentally burning food is never a worry.</p>
<p>For the ease of maintenance, I&#39;d recommend slow cookers whose pot and lid can be taken out for cleaning.  The pot should also be relatively large &#40;3L<sup id="fnref:imperial">[3]</sup> or more&#41; if you want to make other vegetable-rich stews.</p>
<h2 id="ingredients">Ingredients</h2>
<p>As a <a href="https://commons.wikimedia.org/wiki/File:2013-06-08_mechanical_fan_for_hot_air_ballon.jpg">big fan</a> of <a href="https://chefjeanpierre.com">Chef Jean-Pierre</a>, I eyeball the amount of pretty much all ingredients here.  The amount of pork and eggs should be enough to at least fill the bottom of the pot.  I prefer quail eggs for their bite size and leaner cuts of pork but with some intramuscular fat and tendons.  Hocks, hams and shoulders are all good and cheap candidates. Leave the skin on, the gelatine helps thicken the sauce.  I like equal amount of eggs and meat.</p>
<p>For seasoning, you&#39;ll need fish sauce, sugar, whole black pepper, and optionally shallot, garlic and hard coconut meat.</p>
<h2 id="preparation">Preparation</h2>
<p>Boil the eggs and peel them.  Layer them in the pot.  Peel and slice one or two cloves of garlic and sprinkle them in there.  If you have coconut meat, julienne<sup id="fnref:coconut">[4]</sup> and throw it in as well.</p>
<p>Cut the pork into bite-size dice.  Place the skin facing up or the side of the pot.  You want &#40;some of it&#41; to be drier for texture variety. Peel a few cloves of shallot and embed them between the dice of pork.</p>
<h2 id="cooking">Cooking</h2>
<p>Pour a very thin layer of sugar on a sauce pan and heat it up at medium low to make some dark caramel &#40;too low you&#39;ll just get liquid sugar and too high you&#39;ll burn it faster than the <a href="https://nixnet.social/notice/AL2XqGNF2VwKgmbLfc">blue hedgehog</a>&#41;.  Soon as it&#39;s bubbling, carefully pour in some water.  The amount should be able to almost cover the meat and eggs in the pot.</p>
<p>While waiting the caramel to dissolve, add fish sauce to taste, and throw in a generous number of peppercorns.  Transfer the sauce to the pot, making sure the eggs are fully covered &#40;they can be really chewy when dry: another reason to favor the quail ones&#41;.</p>
<p>Turn the slow cooker on low and cook for around eight hours.  Tastes amazing either hot or cold, best served with boiled or pickled vegetables and any kind of starch, commonly rice or sweet potatoes but you can try bread, potatoes, or even short pasta if you&#39;re feeling adventurous.</p>
<table class="fndef" id="fndef:USTH">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">My ole frens from <a href="https://usth.edu.vn">USTH</a> absolutely dug it during military training&#33;</td>
    </tr>
</table><table class="fndef" id="fndef:ngán">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">Okay, maybe I lied about the digging part.</td>
    </tr>
</table><table class="fndef" id="fndef:imperial">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">Or 0.12245589 diesel tank in freedom units.</td>
    </tr>
</table><table class="fndef" id="fndef:coconut">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content">Anywhere between the size of a matchstick and a chopstick is good.</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/kholow@cnx%3E&Subject=Re: Slow Cooked Pork and Eggs">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/kholow@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/kholow/comments.xml</wfw:commentRss>
</item>
<item>
  <title>XML and Photo Gallery Generation: A Love Story</title>
  <link>https://cnx.gdn/blog/pixml/index.html</link>
  <guid>https://cnx.gdn/blog/pixml/index.html</guid>
  <description>How I make my photo gallery in XML and what&#39;s lovely about it</description>
  <category>fun</category><category>recipe</category><category>net</category>
  <pubDate>Fri, 17 Mar 2023 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="xml_and_photo_gallery_generation_a_love_story">XML and Photo Gallery Generation: A Love Story</h1>
<blockquote>
<p>I&#39;m just a language, whose style sheets are good<br />Oh, Lord, please, don&#39;t let me be misunderstood</p>
</blockquote>
<div class="admonition note"><p class="admonition-title">Tips</p><p>As usual, the article starts with a text wall of random rambling. If you are only interested in the technical aspects, feel free to skip the first two sections.</p>
</div>
<div class="franklin-toc"><ol><li>Introduction</li><li>Motivation</li><li>Preliminary</li><li>Approach</li><li>Implementation<ol><li>Page Generation</li><li>Feed Generation</li><li>Thumbnail Generation</li></ol></li><li>Discussion</li><li>Conclusion</li></ol></div>
<h2 id="introduction">Introduction</h2>
<p>Neural-optic live streaming probably, no, definitely offers the most photorealistic graphics one can set eyes on.  <a href="https://en.wikipedia.org/wiki/Computer-generated_imagery">CGI</a> is just a pathetic mimic, and photography or videography is no more than a poor plagiarism attempt when compared to quantum ray-tracing and other advanced physics simulations^W happenings.</p>
<p>On the other hand, we humen are rather shite at replaying visual memories, whilst &#40;<a href="https://en.wikipedia.org/wiki/Data_degradation">bit rot</a> aside&#41; media can be archived <a href="https://xkcd.com/1683">for forever</a>.  Besides, many of us are too busy to <em>touch grass</em> or go see cool things as regularly as we wish to.  This is how an industry based on showing us <a href="https://en.wikipedia.org/wiki/Drama">mundane stuff</a> or <a href="https://en.wikipedia.org/wiki/Fiction">obvious bullcrap</a> can still manage to make tens of thousands of <a href="https://antifandom.com/how-i-met-your-mother/wiki/Crapload">craploads</a> each year any why the interwebs are flooded with pictures of cats, kitties and pussies.</p>
<p>Finding new shits means dopamine dispensation and that&#39;s why <a href="https://www.youtube.com/watch?v&#61;1SNRULEnTVQ">they are dope</a>.  As a model netizen, I adhere to the web&#39;s social contract of mutual <a href="https://fe.disroot.org/@mcsinyx">shitposting</a> so that everyone can have a piece.  Every blue moon, I also enjoy posting more quality stuff like what you are reading right now, should you ignore the number of <a href="https://peervideo.club/w/uByA7Czy7PWYMqnu8FgXvW">Mozart</a> references in the last three paragraphs.</p>
<h2 id="motivation">Motivation</h2>
<p>Some other times, I also want to share the living things and sceneries I encounter in the <a href="https://github.com/zig-community/user-map/pull/120">new</a> place.  My camera was gifted by father before I moved and yet I shared more photos <a href="https://fotofed.nl/cnx">with strangers</a> than with my family.  The PixelFed instance I landed on irreversibly shrank and lossily compressed them, while dumping 5 MB images to the family chat room just feels weird, hence I decided to gather the decency to build a photo gallery to show my loved ones &#40;and admittedly, flex with online strangers&#41;.</p>
<p>There are not many <a href="https://en.wikipedia.org/wiki/Content_management_system">CMS</a> in the wild for photo hosting, and they often acts as a wall garden and/or a social network. Building and hosting a new one is quite overkill, thus the obvious solution left would be generating a static site.  Out of the gazillion <a href="https://en.wikipedia.org/wiki/Static_site_generator">SSG</a>, I couldn&#39;t found any that meets the my requirements:</p>
<ol>
<li><p>Generate a <a href="https://en.wikipedia.org/wiki/Web_feed">web feed</a></p>
</li>
<li><p>Automate filling <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/Img">image</a> title and alt text</p>
</li>
<li><p>Offer fine-grain control for permanent <a href="https://en.wikipedia.org/wiki/Pagination">pagination</a></p>
</li>
<li><p>Generate thumbnails with custom size and name</p>
</li>
</ol>
<p>I mean, they perhaps exist, but the number I had to try and fight through would cost more time than writing the web pages and feed by hand. So I wrote them from scratch.  Y&#39;all can stand up and clap now&#33;</p>
<h2 id="preliminary">Preliminary</h2>
<p>Yes, I really started with writing <a href="https://en.wikipedia.org/wiki/XHTML">XHTML</a> and <a href="https://www.rfc-editor.org/rfc/rfc4287">Atom</a> by hand. A web page has the following structure with namespaces omitted and denoted in WXML &#40;<a href="https://www.draketo.de/software/wisp">Wisp</a>\(\times\)<a href="https://okmij.org/ftp/Scheme/SXML.html">SXML</a>&#41; so I don&#39;t have to close the tags &#40;have I given up on XML too early?-&#41;.</p>
<div class="admonition note"><p class="admonition-title">Syntax hints</p><p>For the uninitiated, any indentation or colon in Wisp represents an additional nest level, while a dot escape the nesting.  The at signs are used by SXML to denote attributes, which may remind you of <a href="https://www.w3.org/TR/xpath">XPath</a>. For example, the anchor to the previous page is <code>&lt;a href&#61;41&gt;PREV&lt;/a&gt;</code>.</p>
</div>
<pre><code class="language-julia">html
  head
    link
      @ : rel &quot;alternate&quot;
          type &quot;application/atom&#43;xml&quot;
          href &quot;/atom.xml&quot;
    ...
  body
    nav
      a : @ : href &quot;41&quot;
        . &quot;PREV&quot;
      h1 &quot;PAGE 42&quot;
      a : @ : href &quot;43&quot;
        . &quot;NEXT&quot;
    article
      @ : id &quot;foobar&quot;
      h2
        a : @ : href &quot;#foobar&quot;
          . &quot;foobar&quot;
      a : @ : href &quot;/42/foo.jpg&quot;
          img
            @ : src &quot;/42/foo.small.jpg&quot;
                alt &quot;pic of foo&quot;
                title &quot;pic of foo&quot;
      a : @ : href &quot;/42/bar.jpg&quot;
          ...
    article ...
    ...
    footer ...</code></pre>
<p>So far, adding an <code>article</code> is not yet too cumbersome, there&#39;s only a bit of redundancy for permanent links and the nesting level is acceptable with the deepest being <code>/html/body/article/a/img</code>.  It gets more repetitive once we publish it to to the linked Atom feed:</p>
<pre><code class="language-julia">feed
  entry
    link
      @ : rel &quot;alternate&quot;
          type &quot;application/xhtml&#43;xml&quot;
          href &quot;https://gallery.example/42/#foobar&quot;
    id &quot;https://gallery.example/42/#foobar&quot;
    title &quot;foobar&quot;
    content
      @ : type &quot;xhtml&quot;
      div
        img
          @ : src &quot;https://gallery.example/42/foo.jpg&quot;
              alt &quot;pic of foo&quot;
              title &quot;pic of foo&quot;
        img ...
    updated ...
  entry ...
  ...</code></pre>
<p>Since web feeds are standalone documents, they must always use absolute URLs. &#40;Welp that&#39;s not entirely true, <a href="https://www.w3.org/TR/xmlbase">XML Base</a> does exists, but not all readers support it, and more importantly, certain elements such as <code>atom:id</code> disallow relative references.&#41;  In addition, whilst the web page links a thumbnail to the original image to save bandwidths, the feed can be consumed one post at a time, which thus points to the full size version.  Therefore, copying the markup to embed it inside the Atom is error-prone and doesn&#39;t exactly spark joy.</p>
<div class="admonition note"><p class="admonition-title">Fun fact</p><p>What does spark joy is that we can embed XHTML directly into the web feed, which means the content is still XML and we don&#39;t need to quote it in CDATA. For other sites where contents don&#39;t accumulate up to hundreds of megabytes, this will allow us to slap some &#40;SPOILER ALERT&#33;&#41; stylesheet on the Atom feed and let the user agent render it in a <a href="https://simonesilvestroni.com/blog/build-a-human-readable-rss-with-jekyll">human-readable form</a>.</p>
</div>
<h2 id="approach">Approach</h2>
<p>I actually already spoiled it in the epigraph,<sup id="fnref:spoiler">[1]</sup> but for the sake of completeness let us <a href="https://xkcd.com/1445">discuss a few possible solutions</a>. What I wanted was to reduce the redundancy of manual input, in other words, a system transforming a custom information-dense format to standard yet sparser ones, which in this case are XHTML and Atom.  Given some new photos and their relevant data, the purpose was to minimize the publishing friction.</p>
<p>It&#39;s worth mentioning that the goal was not to minimize the input format, the transformation speed, or feedback latency, but all of the above, plus the cost of constructing the tool, incrementally as our requirements slightly changes over time.  Our choice for the base <a href="https://programming-journal.org/2023/7/13">programming system</a> shall affect each and every of these aspects and more.</p>
<p>Some technical dimensions are <a href="https://en.wikipedia.org/wiki/Animal_Farm">more equal</a> than others, though. For this use case, IMHO immediate feedback loop should be given the number one priority, not only because it&#39;d be frustrating to have to complete multiple rituals just to preview the changes, but also as watching and reflecting file system changes is &#40;sadly still&#41; a difficult problem.</p>
<p>For Linux<sup id="fnref:interjection">[2]</sup> there&#39;s <a href="https://man7.org/linux/man-pages/man7/inotify.7.html">inotify</a> which doesn&#39;t suck, except when it does and misses events,<sup id="fnref:entr">[3]</sup> and the standard POSIX build tool <a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html">make</a> relies on <a href="https://apenwarr.ca/log/20181113">mtime which is also flaky</a>.  Some SSG work around this by spawning up a server with more sophisticated caching mechanism and even include a HTTP server sending out refresh events. Implementing such system is easily <a href="https://xkcd.com/1319">more expensive</a> than doing the original task manually.</p>
<p>Luckily, there is another way.  <em>After</em> the birth of imperative DOM manipulation programs running on VM inside browsers &#40;Ecma scripts&#41;, there came a &#40;now forgotten&#41; art of purely functional DOM transformation. More specifically, <a href="https://www.w3.org/standards/xml/transformation">XSLT</a> can declaratively transform any XML document to another, and its best part is that modern browsers natively support it, i.e. there&#39;s no difference between editing the input document and the hypothetical output XHTML.  For better portability and rendering performance, we can still generate the latter ahead-of-time &#40;AoT&#41; during deployment.</p>
<h2 id="implementation">Implementation</h2>
<p>Going back to the example, the input format could boil down to a more concise XML file, e.g. <code>42/index.xml</code>:</p>
<pre><code class="language-julia">page
  @ : prev &quot;41&quot;
      curr &quot;42&quot;
      next &quot;43&quot;
  post
    @ : title &quot;foobar&quot;
        time ...
    picture
      @ : filename &quot;foo&quot;
          desc &quot;pic of foo&quot;
    picture ...
    ...
  post ...
  ...</code></pre>
<h3 id="page_generation">Page Generation</h3>
<p>The stylesheet should then be declared at the beginning of the file, so that the user agent can automatically fetch and apply it to render the output XHML:</p>
<pre><code class="language-julia">&lt;?xml-stylesheet href&#61;&quot;/page.xslt&quot; type&#61;&quot;text/xsl&quot;?&gt;</code></pre>
<p>XSLT is essentially a templating language, similar to PHP &#40;which is also older&#41; and template libraries in your favorite languages.  For the ease of reading, I will let the target document&#39;s namespace be the default, while aliasing the transformation one as <code>xsl</code>.  The stylesheet for the web pages would look something like the following, which should be self-explanatory.</p>
<pre><code class="language-julia">xsl:stylesheet
  xsl:template : @ : match &quot;/page&quot;
    xsl:variable : @ : name &quot;base&quot;
      xsl:text &quot;/&quot;
      xsl:value-of : @ : select &quot;@curr&quot;
      xsl:text &quot;/&quot;
    html
      head ...
      body
        nav
          xsl:if : @ : test &quot;@prev &#33;&#61; &#39;&#39;&quot;
            a : @ : href &quot;/&#123;@prev&#125;/&quot;
              . &quot;PREV&quot;
          h1 : xsl:text &quot;PAGE &quot;
               xsl:value-of : @ : select &quot;@curr&quot;
          xsl:if : @ : test &quot;@next &#33;&#61; &#39;&#39;&quot;
            ...
        xsl:for-each : @ : select &quot;post&quot;
          xsl:variable : @ : name &quot;id&quot;
            xsl:value-of
              @ : select &quot;translate&#40;@title, &#39; &#39;, &#39;-&#39;&#41;&quot;
          article
            @ : id &quot;&#123;&#36;id&#125;&quot;
            h2
              a : @ : href &quot;#&#123;&#36;id&#125;&quot;
                  xsl:value-of : @ : select &quot;@title&quot;
            xsl:for-each : @ : select &quot;picture&quot;
              a : @ : href &quot;&#123;&#36;base&#125;&#123;@filename&#125;.jpg&quot;
                  img
                    @ : src &quot;&#123;&#36;base&#125;&#123;@filename&#125;.small.jpg&quot;
                        alt &quot;&#123;@desc&#125;&quot;
                        title &quot;&#123;@desc&#125;&quot;
        footer ...</code></pre>
<h3 id="feed_generation">Feed Generation</h3>
<p>Similarly, for Atom entries on a single page,</p>
<pre><code class="language-julia">xsl:stylesheet
  xsl:variable : @ : name &quot;root&quot;
    . &quot;https://gallery.example/&quot;
  xsl:template : @ : match &quot;/page&quot;
    xsl:variable : @ : name &quot;base&quot;
      xsl:value-of : @ : select &quot;&#36;root&quot;
      xsl:value-of : @ : select &quot;@curr&quot;
      xsl:text &quot;/&quot;
    xsl:for-each : @ : select &quot;post&quot;
      xsl:variable : @ : name &quot;url&quot;
        xsl:value-of : @ : select &quot;&#36;base&quot;
        xsl:text &quot;#&quot;
        xsl:value-of
          @ : select &quot;translate&#40;@title, &#39; &#39;, &#39;-&#39;&#41;&quot;
      entry
        link
          @ : rel &quot;alternate&quot;
              type &quot;application/xhtml&#43;xml&quot;
              href &quot;&#123;&#36;url&#125;&quot;
        id : xsl:value-of : @ : select &quot;&#36;id&quot;
        title : xsl:value-of : @ : select &quot;@title&quot;
        content
          @ : type &quot;xhtml&quot;
          div
            xsl:for-each : @ : select &quot;picture&quot;
              img
                @ : src &quot;&#123;&#36;base&#125;&#123;@filename&#125;.jpg&quot;
                    alt &quot;&#123;@desc&#125;&quot;
                    title &quot;&#123;@desc&#125;&quot;
        updated : xsl:value-of : @ : select &quot;@time&quot;</code></pre>
<p>The trickier part here is concatenating the entries together. Simple enough, instead of linking to the stylesheet in the data, we can read XML files directly from XSLT.</p>
<pre><code class="language-julia">xsl:template
  @ : match &quot;/&quot;
  ...
  xsl:apply-templates
    @ : select &quot;document&#40;&#39;42/index.xml&#39;&#41;/page&quot;
  xsl:apply-templates ...
  ...</code></pre>
<p>This allows us to do other cool things, such as embedding SVG in XHTML to make use of the parent element&#39;s <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#currentcolor_keyword">currentcolor</a>, while keeping the source files separate.  It is especially useful for monochromatic icons, e.g.</p>
<pre><code class="language-julia">xsl:copy-of : @ : select &quot;document&#40;&#39;cc.svg&#39;&#41;/*&quot;
xsl:copy-of : @ : select &quot;document&#40;&#39;by.svg&#39;&#41;/*&quot;
xsl:copy-of : @ : select &quot;document&#40;&#39;sa.svg&#39;&#41;/*&quot;</code></pre>
<h3 id="thumbnail_generation">Thumbnail Generation</h3>
<p>So far, we have met three out of the four requirements, only thing left is creating the thumbnails.  Inspired by Ethan Dalool, I am going for <a href="https://voussoir.net/writing/sharing_photos">fairly large ones of 1024 px in width</a>,</p>
<blockquote>
<p>large enough to comfortably browse the photos without clicking through to the big version of each, and the thumbnails are decently light and not too jpeggy at about 125-150 kilobytes on average.</p>
</blockquote>
<p>At such size, I can aim for around ten photoes<sup id="fnref:toes">[4]</sup> per page while maintaining a somewhat decent load time.  Plus, since the width of images are hardcoded, page <a href="https://en.wikipedia.org/wiki/Margin_&#40;typography&#41;">margin</a> could be automatically inferred to never stretch them.</p>
<pre><code class="language-css">html &#123;
    box-sizing: border-box;
    margin: auto;
    max-width: calc&#40;1024px &#43; 2ch&#41;;
&#125;
body &#123; margin: 0 1ch &#125;</code></pre>
<p>To generate the thumbnails, I use <a href="https://github.com/mattes/epeg">epeg</a> together with <code>make</code> for wildcarding:</p>
<pre><code class="language-julia">PICTURES :&#61; &#36;&#40;filter-out &#37;.small.jpg &#36;&#40;PREFIX&#41;/&#37;.jpg, &#36;&#40;wildcard */*.jpg&#41;&#41;
THUMBNAILS :&#61; &#36;&#40;patsubst &#37;.jpg,&#37;.small.jpg,&#36;&#40;PICTURES&#41;&#41;&#37;.small.jpg: &#37;.jpg
	epeg -w 1024 -p -q 80 &#36;&lt; &#36;@</code></pre>
<p>The Makefile also define rules for AoT compilation using <a href="https://gnome.pages.gitlab.gnome.org/libxslt/xsltproc.html">xsltproc</a> for the web pages and feed.  Apparently no feed reader supports XSLT, and for pages runtime processing negatively affect the performance due to the multiple round trips for the stylesheet and the vector icons.</p>
<pre><code class="language-julia">DATA :&#61; &#36;&#40;wildcard */index.xml&#41; index.xml
PAGES :&#61; &#36;&#40;patsubst &#37;.xml,&#37;.xhtml,&#36;&#40;DATA&#41;&#41;
OUTPUTS :&#61; &#36;&#40;THUMBNAILS&#41; &#36;&#40;PAGES&#41; atom.xmlall: &#36;&#40;OUTPUTS&#41;index.xml: &#36;&#40;LATEST&#41;/index.xml
	ln -fs &#36;&lt; &#36;@&#37;.xhtml: &#37;.xml page.xslt
	xsltproc page.xslt &#36;&lt; &gt; &#36;@atom.xml: atom.xslt &#36;&#40;DATA&#41; &#36;&#40;wildcard *.svg&#41;
	xsltproc atom.xslt &gt; atom.xml</code></pre>
<p>The <a href="https://trong.loang.net/~cnx/px">full implementation</a> is deployed to <a href="https://px.cnx.gdn">px.cnx.gdn</a>, mirrored to the <a href="https://www.opennic.org">OpenNIC</a> domain <a href="https://pix.sinyx.indy">pix.sinyx.indy</a> reusing the former&#39;s TLS certificate, because CA/Browser Forum disallows support for domains not recognized by ICANN and no <a href="https://wiki.opennic.org/opennic/tls">CA for OpenNIC</a> is mature enough.</p>
<h2 id="discussion">Discussion</h2>
<blockquote>
<p><em>Okay you built your site using XML macros, so what? The syntax is clunky and you hate it so much yourself that not even a single line of code example here is in actual XML. Doesn&#39;t seem like a love story to me&#33;</em></p>
</blockquote>
<p>Like all relationships, it&#39;s not that simple.  I&#39;ve learned to not judge a book by its cover and come to the understanding that XML is the &#40;ugly&#41; equivalence of <a href="https://en.wikipedia.org/wiki/S-expression">sexp</a>.<sup id="fnref:sex">[5]</sup>  Unlike afterthoughts such as C preprocessors, <a href="https://docs.djangoproject.com/en/dev/topics/templates">Django</a>-like templates, or even the Wisp-lookalike syntax of <a href="https://github.com/slim-template/slim">Slim</a>, XML stylesheets is in the same data structure.  To put it another way, one can use XSLT to generate XSLT from XSLT.  Do I need it in this case or ever at all?  Probably not, but that certainly makes XSL a lot more attractive in my eyes.</p>
<p>Furthermore, the tooling for XML is highly mature, from editors to linters and processors to rendering engines.  It&#39;d be lying to say you ain&#39;t fascinated that tis possible to directly feed browsers pure data instead of markup representations.  More than that, one can have entirely static API endpoints that are both human- and machine-readable.</p>
<blockquote>
<p><em>XSL is just declarative JS&#33;  You are so blinded by your lust for functional programming that you have become <a href="https://cnx.gdn/blog/reply">the very thing you swore to destroy</a>&#33;</em></p>
</blockquote>
<p>My distaste for Ecma scripts is not due to DOM manipulation. Sure, I do find in-place modification inelegant for documents, but if only that&#39;s the only issue.  I block them on most sites because they can interact with many things other than just the DOM, imposing <a href="https://en.wikipedia.org/wiki/Mouse_tracking">privacy</a> and <a href="https://react-etc.net/entry/exploiting-speculative-execution-meltdown-spectre-via-javascript">security</a> risks while <a href="https://meta.stackexchange.com/q/2980/698165">fucking up the UX</a>.</p>
<p>Architecturally, Ecma scripts enable the absolute bloody worst possible kind of web pages with zero data at all, fetching tiny pieces of content in JSON and turn performance <a href="https://unixsheikh.com/articles/so-called-modern-web-developers-are-the-culprits.html">to shit</a>.  The user agents then try to salvage efficiency by turning themselves into a distributed system component and adding optimizations that shall never be &#40;ab&#41;used for the sake of users. O ye <a href="https://en.wikipedia.org/wiki/Wirth&#37;27s_law">cycle of doom</a>&#33;</p>
<p>Note that one can make a similar mistake with XSL regarding the number of round trips, and XML stylesheets can provide the same front-end/back-end separation.  Both can be used to provide hot loading during development and AoT rendering in production &#40;if not all, then many JS libraries support pre-rendering, ignoring the monstrous <a href="https://cnx.gdn/blog/dedep">dependency graph</a>&#41;. At the end of the day, it&#39;s not the matter of technology but principle: to be in the <a href="https://pluralistic.net/2023/01/21/potemkin-ai/#hey-guys">users&#39; best interest</a>.</p>
<blockquote>
<p><em>There is nothing complex about the photo gallery, any existing SSG can do the same with minor tweaks&#33; You never needed to write a new one to begin with&#33;</em></p>
</blockquote>
<p>I am wondering the same myself, but keep in mind there are details I&#39;ve been hiding from in the example.  I went all-in for the semantic web with the hope for best portability and accessibility.  One thing I haven&#39;t mentioned is the <code>lang</code> attribute, e.g. <code>en</code>, <code>vi</code> or <code>fr</code> depending on the post.  Adding this to the web pages requires the SSG to be somewhat modular, and even harder for the web feed.</p>
<p>Moreover, generic SSG are not designed to handle the difference in content between a page&#39;s <code>article</code> and the feed&#39;s corresponding <code>entry</code>, neither for having multiple posts in a single page.  Pagination is also commonly implemented backwards, i.e. page 2 being the second latest one, making it impossible to avoid link rot.</p>
<p>Not to suggest that the majority of SSG are poorly designed, just that from a certain amount of <a href="https://guide.handmade-seattle.com/c/2021/context-is-everything">context</a> difference, tis cheaper to just redesign from scratch.  This is not about XSL vs Go/Python/JS for SSG or web dev in general, but this specific and happen-to-be-far-from-complex case.</p>
<h2 id="conclusion">Conclusion</h2>
<p>At the time of writing, XML has pretty much been superseded by JSON or YAML, for the better or worse.  I have no love for YAML for obvious reasons, but it also saddens me to sometimes see JSON being solely used as a container for HTML.  I hope that this essay can <a href="https://www.youtube.com/watch?v&#61;F3QPWrLFsOA">awaken something in you</a> about XML and remind you about the semantic web in your next project.  It worked out for me, maybe it&#39;ll work out for you too&#33;</p>
<p>The story between XML and my photo gallery is a fond love story. They were born for each other, there was no drama, everything just werkt. Their romance inspire me to better appreciate stability and maturity, and value those right in front of my eyes yet I had been <em>too blind to see</em>. Anyway, this is getting too long, so Imma end it with another <a href="https://www.youtube.com/watch?v&#61;5LvOdWi3Qno">song</a>.</p>
<blockquote>
<p>Lookin&#39; for perfect<br />Surrounded by artificial<br />You&#39;re the closest thing to real I&#39;ve seen<br />Sure, everyone has their problems<br />That&#39;s a given<br />Yours are the easiest to tolerate</p>
</blockquote>
<table class="fndef" id="fndef:spoiler">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">If you know, you know.</td>
    </tr>
</table><table class="fndef" id="fndef:interjection">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">Yup, just the kernel.</td>
    </tr>
</table><table class="fndef" id="fndef:entr">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">But in case it works for you, check out <a href="https://eradman.com/entrproject">entr</a>.</td>
    </tr>
</table><table class="fndef" id="fndef:toes">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content"><em>Thumb</em>nails, pho<em>toes</em>, get it?-&#41;</td>
    </tr>
</table><table class="fndef" id="fndef:sex">
    <tr>
        <td class="fndef-backref">[5]</td>
        <td class="fndef-content">Or conventionally in most Lisp 1&#39;s, <code>sex?</code>.</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/pixml@cnx%3E&Subject=Re: XML and Photo Gallery Generation: A Love Story">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/pixml@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/pixml/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Artisanal GIF Ripping</title>
  <link>https://cnx.gdn/blog/gifrip/index.html</link>
  <guid>https://cnx.gdn/blog/gifrip/index.html</guid>
  <description>How to make GIF files from videos</description>
  <category>fun</category><category>recipe</category>
  <pubDate>Thu, 16 Mar 2023 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="artisanal_gif_ripping">Artisanal GIF Ripping</h1>
<div class="admonition note"><p class="admonition-title">Pronounciation</p><p>/dʒɪf/ is the format, /ɡɪf/ is the handball club in Eskilstuna, Sweden.</p>
</div>
<p>GIF, <em>the</em> graphics interchange format, is probably the most <a href="https://telegram.org/blog/gif-revolution">inefficient</a> representation for animated images in quality/size that is widespread today. However, it does live up to its name, being also the most portable format for animated graphics exchange.  If a device has a color display and is connected to the Internet, tis likely to support GIF out of the box.</p>
<p>Like with incandescent light bulbs, it&#39;d be wasteful to have not switched to more efficient alternatives, though GIF still has its charms.  Not having to worry about codec compatibility is one thing, the nostalgia induced by the <code>.gif</code> file extension is another.  For years, I got an <em>Internet</em> folder full of those &#40;along with still images and short videos&#41; for offline viewing, shitposting and reaction.</p>
<p>More recently, I <a href="https://nixnet.social/notice/A8VniqEBKfJvMc2dTE">began</a> <a href="https://nixnet.social/notice/A9gMc47yxgoTNDIa7U">to</a> <a href="https://fe.disroot.org/notice/ANK2GqGxIdcDGBRAFU">make</a> <a href="https://nixnet.social/notice/AOhGjOUwKJmiEFNLkG">my</a> <a href="https://fe.disroot.org/notice/APhUh2H8radlKKIZGa">own</a> <a href="https://fe.disroot.org/notice/APhUh2H8radlKKIZGa">animated</a> <a href="https://larkspur.one/notice/ATe4i9UvhxjiFvXprs">images</a> and take pride in them.  This tutorial will step-by-step lay out my process in cutting out a high-quality GIF from a video, so <a href="https://www.youtube.com/watch?v&#61;TVMK2gQig4A">you can be just like me</a>&#33;</p>
<div class="franklin-toc"><ol><li>Decide on the Format</li><li>Extract Frames</li><li>Crop and Resize</li><li>Combine</li></ol></div>
<h2 id="decide_on_the_format">Decide on the Format</h2>
<blockquote>
<p>Just because you can doesn&#39;t mean that you should.</p>
</blockquote>
<p>Good things often don&#39;t come out of desire, but necessity. GIF is cool because tis portable, but plain text is even more portable. Multimedia weigh hundreds of kilobytes each, so they better best convey whatever information they&#39;re meant to communicate.  Don&#39;t replace the audio with a subtitle just so it can be an animated image. Don&#39;t loop anything longer than a few seconds.</p>
<p>There is an old saying: <em>if tis doing fine being a video, let it be a video</em>. Videos don&#39;t need each frame to be perfect, and thus all the following steps can be done in a single ffmpeg command.  Work smart, even just for shitposting.</p>
<h2 id="extract_frames">Extract Frames</h2>
<p>Open the source video in <a href="https://mpv.io">mpv</a>, seek then spam <code>s.</code> repeatedly. That&#39;s all, y waste time write lot word wen few word do trick?</p>
<p>Aight, maybe there&#39;s a bit more to it.  Operating on the level of frames will allow you to skip redundant ones in case of misencoded sources or duplicate and reverse a subsequence for a closer-to-perfect loop, but really, there&#39;s not much to talk about.</p>
<h2 id="crop_and_resize">Crop and Resize</h2>
<p>Ripping a GIF from a video is taking a sequence of frames out of context. Framing and some objects in the scene might not make sense in the target animated image.  Some also like to export videos with giant black bars just to fuck with us.  That&#39;s where cropping comes into play.  For measuring I use <a href="https://www.gimp.org">GIMP</a>, which doesn&#39;t seem to be the right tool for the job, so please let me know of anything lighter that has a ruler.</p>
<p>In addition, videos from social media &#40;and space-efficient movies&#41; are heavily compressed and look pretty bad for their resolution.  Usually I have to shrink them down to two third or a half of their original width for them to look decently sharp.  When you have the geometry in mind, summon the <a href="https://imagemagick.org">image wizard</a>:</p>
<pre><code class="language-sh">crop&#61;&quot;-crop &#36;&#123;w&#125;x&#36;&#123;h&#125;&#43;&#36;&#123;dx&#125;&#43;&#36;&#123;dy&#125;&quot;
resize&#61;&quot;-resize &#36;&#123;width&#125;x&#36;&#123;height&#125;&quot;
parallel -i sh -c &quot;convert &#36;crop &#36;resize&quot;\
&#39; &#123;&#125; &#36;&#40;basename &#123;&#125; .jpg&#41;.png&#39; -- mpv-shot*.jpg</code></pre>
<p>In case you don&#39;t have <a href="https://joeyh.name/code/moreutils">moreutils</a>/<a href="https://www.gnu.org/software/parallel">GNU parallel</a>, or if you are rocking a single-CPU machine, run:</p>
<pre><code class="language-sh">mogrify &#36;crop &#36;resize -format png mpv-shot*.jpg</code></pre>
<p>For resizing, image processing expert Nicolas Robidoux wrote a long article on <a href="https://imagemagick.org/Usage/filter/nicolas">resampling filters</a>.  Although I can&#39;t spot any distinction for this use case, it doesn&#39;t hurt to give it a read and play around with different options.</p>
<p><img src="https://cnx.gdn/assets/same-picture.png" alt="Pam stating that they are the same picture" /></p>
<h2 id="combine">Combine</h2>
<p>We are going to encode in best quality possible using <a href="https://gif.ski">gifski</a>. The reason we converted from JPEG to PNG earlier is that our precious encoder refuses to touch any other <em>image</em> format, and that mpv take screenshots in JPEG by default &#40;you can <a href="https://mpv.io/manual/master/#screenshot">configure</a> it to write in lossless format but in my experience, the source videos are often too heavily compressed for it to make any difference&#41;.  Anyhow, invoking gifski is rather straightforward:</p>
<pre><code class="language-sh">gifski -r &#36;fps -Q 100 -o &#36;name.gif mpv-shot*.png</code></pre>
<p>Note that it optionally takes width and height as an argument and yield better quality than with pre-shrunken images, but at the cost of a significantly larger file size.  Choose wisely.</p>
<p></p>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/gifrip@cnx%3E&Subject=Re: Artisanal GIF Ripping">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/gifrip@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/gifrip/comments.xml</wfw:commentRss>
</item>
<item>
  <title>The 2020 Experience</title>
  <link>https://cnx.gdn/blog/2020/index.html</link>
  <guid>https://cnx.gdn/blog/2020/index.html</guid>
  <description>My life in 2020</description>
  <category>lyf</category><category>exp</category>
  <pubDate>Sat, 07 Jan 2023 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="the_2020_experience">The 2020 Experience</h1>
<div class="admonition note"><p class="admonition-title">Not to be confused with <em>The 20/20 Experience</em></p></div>
<div class="franklin-toc"><ol><li>The Germination</li><li>The Fruition</li><li>The Disease</li><li>The Profit</li><li>The Migrations</li><li>The Moral</li></ol></div>
<h2 id="the_germination">The Germination</h2>
<p>To understand my 2020, we have to travel back a few months, when it all started.  No, not <em>that thing</em> beginning at the end of &#39;19. I am talking about <em>my</em> 2020 experience, remember?</p>
<p>The story started in October 1810 in the not-so-little city of Munich, Germany. Alright, it sounds like I lied about the 2019 and my story part, but bear with me, it&#39;s all connected.  Anyhow, some Bavarian couple got married and threw a big party.  People like parties, so naturally they celebrated the anniversaries, year after year until it became a tradition known to in English as the <a href="https://en.wikipedia.org/wiki/Oktoberfest">Oktoberfest</a>.</p>
<p>Over two centuries years later, on the wedding day of another Bavarian couple,<sup id="fnref:wedding">[1]</sup> DigitalOcean began to an annual PR campaign on the same month called Hacktoberfest.  I know, to many of you maintaining projects on GitHub &#40;and more recently GitLab.com&#41;, the name might not remind you of something festive, but it really opened a new chapter in my life.</p>
<p><a href="https://www.whoismrrobot.com">Back to the future</a> in 2019, it was my first year taking part in the event. The premise was that one would receive a t-shirt after having filed at least four GitHub Pull Requests™.<sup id="fnref:pr">[2]</sup>  Unlike <em>plethora</em>, this does not sound like it was a lot, yet more than I ever had done.  Getting out of my comfort zone was the first baby step, opening various opportunities in the upcoming months and perhaps, years.</p><figure>
  <img src=https://cnx.gdn/assets/codersrank.png
       alt='Graph showing steeper growth from October 2019'>
  <figcaption>My activities on GitHub over the years</figcaption>
</figure><h2 id="the_fruition">The Fruition</h2>
<p>Probably what I benefited the most from participating in Hacktoberfest was learning to not be afraid of communicating with complete strangers maintaining the software I use.  Stepping into 2020, I started to do a larger variety of stuff in Python, which made installing libraries happen on a regular basis.  The international Internet connection from home at the time was unstable and usually downloads from the package warehouse was a few kBps and that definitely did not help.  A few moments later, I found myself on <a href="https://pypa.io">PyPA</a>&#39;s IRC channel discussing strategies to speed up pip downloading.</p>
<p>After several days of on-off conversations &#40;mostly I was asking questions to fill in the blanks&#41;, a proposal was under draft: I was an undergrad sophomore and had been eyeing on Google Summer of Code &#40;GSoC&#41; for quite a while. Applying for pip wasn&#39;t the plan, but rather <a href="https://octave.org">Octave</a>, the first big project I have contributed code to.<sup id="fnref:1st">[3]</sup>  Now thinking about it, it was a better choice since I was more comfortable with pip&#39;s tech stack. The <a href="https://cnx.gdn/blog/2020/gsoc">rest of the story</a> was already noted down so I won&#39;t be retelling it here.</p>
<h2 id="the_disease">The Disease</h2>
<p>When the world had been battling SARS-CoV-2 for a few months, Việt Nam was barely affected.  By refusing inbound travelers and temporary switching to work/study-from-home, the number of cases and deaths was neglible and by the end of summer we were virtually back to normal.  I hated that most organizations, my university included, straight up offered big techs our data without a second thought, and was thankful online learning did not last.</p>
<p>Like many others, I spent that summer rarely leaving the house.  I was grateful of GSoC for keeping me busy and giving me the opportunity to socialize with new cool people.  It was impossible for me to catch <em>the</em> virus, I thought. I was not wrong though, but I got something else: <a href="https://en.wikipedia.org/wiki/Dengue_fever">dengue fever</a>.  The fever wasn&#39;t too bad, I was high as a kite for half a week, but never critical. The aftermath, however, was much less pleasant.</p>
<p>For the next week, I was in a living hell because of a throat infection. I&#39;d had sore throats before, quite regularly in fact, often at least once every few months, but they had been a mere inconvenience.  Usually, all I&#39;d gotta do had been to <a href="https://en.wiktionary.org/wiki/person_up">person up</a>, swallow a few times and get on with my day.  This was different.  Everything hurt like a bitch.  The slightest texture or flavor could cause minutes of pain.  For the first time, I experience throat lozenges being the opposite of soothing.</p>
<p>For the entire week, I survived on undercooked scrambled eggs and mushy porridge.  I had to take α-chymotrypsin<sup id="fnref:choay">[4]</sup> before every meal and was practically microdosing it throughout the day to be able to drink water.  You can&#39;t imagine how happy I was when I could finally eat rice again.  While the infection was not directly caused by dengue &#40;it only weakened my immune system&#41;, the trauma was enough to make me finally care about home mosquito eradication.  Guess who learnt it the hard way&#33;</p>
<h2 id="the_profit">The Profit</h2>
<p>GSoC gave me in stipend 3000 USD, minus Payoneer fees and shitty currency exchange &quot;tax&quot;.  That was the largest sum I&#39;d ever had in my hands. Because of the low cost of living in Việt Nam,<sup id="fnref:cost">[5]</sup> I no longer completely financially dependent on my parents.  I could pay my own school fees &#40;scholarship would give back the money <em>months</em> after paying&#41;, hang out more with friends &#40;we had zero-COVID for a while, remember?&#41;, tip free software projects and services I had &#40;and have&#41; been using for years.</p>
<p>More importantly, I could buy myself <em>future</em> e-waste.  I got a <a href="https://www.pckeyboard.com">Model M</a> so that I no longer need to change keyboard every year, a <a href="https://video.hardlimit.com/w/uucN1eWVurTSzY325PLS2s">lefty</a> <a href="https://ploopy.co">Ploopy</a> to ease my traffic-accident-injured right wrist that&#39;s prolly never gonna fully heal, a <a href="https://nixnet.social/notice/AI9eETauDunmiiIfHE">new DAP to replace my dead walk man</a>, my <a href="https://cnx.gdn/blog/2020/gsoc/article/4/#snap_back_to_reality">first phone</a> and perhaps some other things.  <a href="https://www.youtube.com/watch?v&#61;5z25pGEGBM4">No worries</a>, I&#39;m still daily driving them today, they ain&#39;t ended up in the landfill &#40;yet&#41;.</p>
<h2 id="the_migrations">The Migrations</h2>
<p>Admittedly, the first <em><a href="https://freedesktop.org">freedesktop.org</a> smartphone</em> caught my eye was actually the Librem 5, which I could afford neither the time nor the money for. I know, the terminology sounds ridiculous, but <em>Linux</em> would include Android and <em>GNU</em>&#39;d exclude <a href="https://postmarketos.org">postmarketOS</a>.  Anyway, <a href="https://puri.sm">Purism</a>, the company behind the Librems, has seriously invested in adaptive GUI and federated services. My first <a href="https://activitypub.rocks">ActivityPub</a> account was provided by <a href="https://librem.one">Librem One</a>.</p>
<p>It was not the first time I use a federated service.  I&#39;ve used email for as long as I can remember and begun to use <a href="https://matrix.org">Matrix</a> intensively since I entered university.  So what &#40;were there to be&#41; changed? At the time, my online presence<sup id="fnref:jargon">[6]</sup> was primarily inside <a href="https://github.com/McSinyx/mcsinyx.github.io/commit/af8e02ec3989.patch">surveillance capitalist walled gardens</a>.  I was mostly active&#40;ly posting&#41; on bird site socializing with people I acquainted during my GSoC and publishing my development/shitpost<sup id="fnref:log">[7]</sup> videos to YouTube.</p>
<p>Nothing on fedi really caught my eyes, until I got &#40;hyped up for getting&#41; my PinePhone.  Its software landscape was incredibly fast moving back then. Most peripherals were barely working.  Desktop programs were being ported for narrower screens using brand new convergent libraries.  Many developers were contracted by Purism or sponsored by Pine64, a large fraction of whom are free software purists, rejecting spyware disguised as social media. Never before, hanging out in chat rooms<sup id="fnref:bridge">[8]</sup> and the Fediverse were the absolutely best ways to keep up with life-quality-changing updates.</p>
<p>Like with desktop-handheld convergence, I was impressed with Fediverse&#39;s interoperability between multiple media formats, from &#40;micro&#41;blogs to picture albums to videos.  Imagine being able to share and comment on a YouTube directly from Twitter&#33;  Shortly, I registered for a <a href="https://joinpeertube.org">PeerTube</a> account and migrate all my videos there.  The longer I stayed on fedi, the more cool stuff I found and the more satisfied I was.  Fast forward over two years, I have deleted or permanently logged out of most; only quiddit<sup id="fnref:reddit">[9]</sup> is left.</p>
<p>One thing led to another, <a href="https://blog.brixit.nl/apps">Martijn Braam&#39;s apps</a> introduced me to <a href="https://sourcehut.org">SourceHut</a>, which embraces email for federation and focuses on useful stuff like <a href="https://man.sr.ht/builds.sr.ht/build-ssh.md">SSH for CI</a>, instead of trying to be a <a href="https://arxiv.org/abs/2006.02371">social media</a> or <a href="https://githubcopilotlitigation.com">relicense the projects it hosts</a>. I have moved most of the software I maintain <a href="https://GiveUpGitHub.org">from GitHub</a> to sr.ht, but the network effect is too strong: I still have to stick around with the former to contribute to software I regularly use.</p>
<p>However, it&#39;s unlikely that most of those growing up with GitHub, especially inexperienced contributors, will be <a href="https://adol.pw/2022/05/09/maintaining-first-project-part-iv-end">willing to adapt to a workflow revolving around mailing lists</a> for such kind of forge to become mainstream again.  On the bright side, I start to seeing more larger projects hosting their development platform, and I am watching <a href="https://forgefriends.org/blog/2022/06/30/2022-06-state-forge-federation">forge federation</a> with great interest.</p>
<h2 id="the_moral">The Moral</h2>
<p>At this point, you probably wonder, what I am trying to tell from all these random rambling.  Welp, nothing.  My life is <a href="https://www.youtube.com/watch?v&#61;9ewTkrfaWtA">not like the movies</a>, there ain&#39;t no plot, no meaning.  The whole point of this log is to bridge the gap between <a href="https://cnx.gdn/blog">/blog</a> and <a href="https://cnx.gdn/blog/2020/gsoc">/blog/2020/gsoc</a>.  2020 was indeed positively life-changing for me, tho/so I can&#39;t expect most of y&#39;all&#39;ll be able to relate.  2023 is already underway, and I hope we will all have a year we can look back to the same way I did in this post.  <a href="https://fe.disroot.org/@mcsinyx/posts/ALaW77HgCSPq4pLxpo">Perchance.</a></p>
<table class="fndef" id="fndef:wedding">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">There must be at least one wedding everyday in Bavaria, I think.</td>
    </tr>
</table><table class="fndef" id="fndef:pr">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">It is a vendor locked-in version of <a href="https://git-scm.com/docs/git-request-pull">git-request-pull</a>.</td>
    </tr>
</table><table class="fndef" id="fndef:1st">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">Not counting Vim because it was a <a href="https://cnx.gdn/works/#simplified_vietnamese_keymaps">keymap</a> contribution.</td>
    </tr>
</table><table class="fndef" id="fndef:choay">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content">Proteolytic enzyme; taken orally for inflammation.  Shit&#39;s magic.</td>
    </tr>
</table><table class="fndef" id="fndef:cost">
    <tr>
        <td class="fndef-backref">[5]</td>
        <td class="fndef-content">A meal at a diner costed around 1 USD at the time.</td>
    </tr>
</table><table class="fndef" id="fndef:jargon">
    <tr>
        <td class="fndef-backref">[6]</td>
        <td class="fndef-content">Gah, I hate this term&#33;</td>
    </tr>
</table><table class="fndef" id="fndef:log">
    <tr>
        <td class="fndef-backref">[7]</td>
        <td class="fndef-content">I don&#39;t like keeping too serious logs.</td>
    </tr>
</table><table class="fndef" id="fndef:bridge">
    <tr>
        <td class="fndef-backref">[8]</td>
        <td class="fndef-content">A room was bridged between 5 protocols, fun but also an eye sore.</td>
    </tr>
</table><table class="fndef" id="fndef:reddit">
    <tr>
        <td class="fndef-backref">[9]</td>
        <td class="fndef-content">Hey, the site name was a pun on <em>read it</em> in the first place&#33;</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/2020@cnx%3E&Subject=Re: The 2020 Experience">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/2020@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/2020/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Advent of Programming Languages</title>
  <link>https://cnx.gdn/blog/advent/index.html</link>
  <guid>https://cnx.gdn/blog/advent/index.html</guid>
  <description>Doing Advent of Code in a new programming language each day</description>
  <category>fun</category><category>exp</category>
  <pubDate>Mon, 26 Dec 2022 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="advent_of_programming_languages">Advent of Programming Languages</h1>
<p>Earlier this year I enrolled in a master&#39;s programme<sup id="fnref:master">[1]</sup> at <a href="https://unist.ac.kr">UNIST</a> and joined the Programming Languages and Software Engineering lab &#40;PLaSE&#41; as a student researcher.  The stipend covers the school fees and living expenses, and I&#39;m given <em>an</em> academic freedom to choose what to work on and take risks. I will review the life here in detail in another post, but &#40;SPOILER ALERT&#33;&#41; overall I&#39;m quite content with it.</p>
<p>That being said, PLaSE is new and small, we only do research on software engineering and don&#39;t do its name justice.  Because of that, in the first year here I decided to do each day of <a href="https://adventofcode.com">Advent of Code</a><sup id="fnref:2021">[2]</sup> in a language I&#39;d never used in competitive programming &#40;CP&#41; before.</p>
<p><img src="https://cnx.gdn/assets/mr-worldwide.jpg" alt="Pitbull holding the globe, captioned: Mr. Worldwide" /></p>
<p>Here was my blacklist going in, chronologically: Pascal, Python, Scheme, C, C&#43;&#43;, Common Lisp, Lua, Raku, Go, Rust and Zig.  I am only proficient in over half of the listed languages, but dura lex, sed lex, I&#39;d already had my CP first time with the rest.</p>
<p>To try any new language, all I have to do is dropping into an ephemeral shell with its implementation using <code>nix-shell</code> or <code>guix shell</code> without the fear of bloating up my systems.  I&#39;m running <a href="https://cnx.gdn/blog/butter">NixOS on my laptop</a> with <a href="https://search.nixos.org/packages">nixpkgs</a> being one of the largest downstream repositories, including everything but the kitchen sink.  On the work desktop, I installed Guix System which has a decent <a href="https://packages.guix.gnu.org">set of packages</a> and <a href="https://trong.loang.net/~cnx/dotfiles/tree/guix/system.scm?id&#61;b53f96565b8c#n51">nix service</a> in case something is missing.  Every update, I run <a href="https://nixos.org/manual/nix/stable/command-ref/nix-collect-garbage.html">garbage</a> <a href="https://guix.gnu.org/manual/en/html_node/Invoking-guix-gc.html">collection</a> and get rid of all unnecessary software, i.e. those not <a href="https://trong.loang.net/~cnx/dotfiles/tree/guix">declared</a> <a href="https://trong.loang.net/~cnx/dotfiles/tree/nix">in my config</a>.</p>
<h2 id="day_one">Day One</h2>
<p>The first day should have been the warm up so I challenged myself with using POSIX utilities.  This is a bit irony though as the majority of my time spent outside of <a href="https://www.vim.org"><em>the</em> editor</a> or a web browser is inside a &#40;<a href="https://www.youtube.com/watch?v&#61;k5E6CExu204">Bourn-again</a>&#41; shell.</p>
<p>The <a href="https://adventofcode.com/2022/day/1">problem</a> was indeed simple, involving only <a href="https://trong.loang.net/~cnx/cp/commit?id&#61;ff0bb53c15dd">finding the maxima among the sums of newline-separated numbers</a>.  I used <a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sed.html">sed</a>&#40;1p&#41; to turn the input into <a href="https://linux.die.net/man/1/dc">dc</a>&#40;1&#41; eypressions, and <a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/sort.html">sort</a>&#40;1p&#41; and <a href="https://pubs.opengroup.org/onlinepubs/9699919799/utilities/tail.html">tail</a>&#40;1p&#41; for picking the largest sum.  Probably the most interesting part was that the summation was reusable to <a href="https://larkspur.one/notice/AQALVP69wAiotsVmgC">grade an assignment</a> for a course I was a teaching assistant for.</p>
<h2 id="day_two">Day Two</h2>
<p>The <a href="https://adventofcode.com/2022/day/2">second problem</a> didn&#39;t ramp up much in difficulty. It only called for some rather <a href="https://trong.loang.net/~cnx/cp/commit?id&#61;ada3a69b15ff">simple arithmetic</a>, and the input format&#39;s regularity convinced me to finally give <a href="https://harelang.org">Hare</a> a try.</p>
<p>For just a taste, Hare is boring in a good way.  I was excited for the tagged union of <a href="https://harelang.org/tutorials/introduction/#defining-new-error-types">error which can include and propagate any debugging information</a>, but unfortunately it wasn&#39;t needed for programs of such complexity &#40;nor that errors are ever handled in CP&#41;. I&#39;m looking forward to a chance to write more Hare in the future.</p>
<h2 id="day_three">Day Three</h2>
<p>The <a href="https://adventofcode.com/2022/day/3">task for day 3</a> was literally day \(1 + 2\) in scope. I went for another <em>better C</em> that is <a href="https://nim-lang.org">Nim</a>.  My first impression with it wasn&#39;t positive: Nim insists on considering each source file as a module and does not allow hyphens in identifier name, so <a href="https://forum.nim-lang.org/t/5024">filenames mustn&#39;t have any hyphen</a> either.  This had led me to piping the source code to <code>nim c -</code> and executing <code>~/.cache/nim/stdinfile_d/stdinfile</code> to keep my solution naming convention. <code>nim r -</code> wouldn&#39;t have worked either since the convention also consists of reading the input from stdin.</p>
<p>On the bright side, <a href="https://en.wikipedia.org/wiki/Uniform_Function_Call_Syntax">uniform function call syntax</a>, identifier case-&#40;and underscore-&#41;insensitivity and optional parentheses allowed me to <a href="https://trong.loang.net/~cnx/cp/commit?id&#61;eeb9a45346a8">dodge parentheses in calls and camelCasing altogether</a>. Although I <em>love</em> Lisp and don&#39;t have any problem with brackets, I think their placement in ALGOL style hurts the readability of nested calls and <a href="https://www.cs.kent.edu/~jmaletic/papers/ICPC2010-CamelCaseUnderScoreClouds.pdf">camelCase is just objectively bad</a>, pun<sup id="fnref:obj">[3]</sup> unintended.</p>
<h2 id="day_four">Day Four</h2>
<p>The <a href="https://adventofcode.com/2022/day/4">forth problem</a> wasn&#39;t any harder, only requiring <a href="https://trong.loang.net/~cnx/cp/commit?id&#61;8941f621840f">simple logic operations and summation</a>.  To save time, I opted for <a href="https://julialang.org">Julia</a>, which I was kinda sorta familiar with in building this site &#40;at the time this is published at least&#41;. Like Nim, it has higher-order functions and a &#40;reference&#41; compiler capable of producing fast binaries.</p>
<h2 id="day_five">Day Five</h2>
<p>The <a href="https://adventofcode.com/2022/day/5">next day&#39;s task</a> was finally a breath of fresh air with <a href="https://trong.loang.net/~cnx/cp/commit?id&#61;aa7616140a8b">matrix parsing and LIFO &#40;literal&#41; stacks</a>.  It begged for a regular expression parser,<sup id="fnref:re">[4]</sup> hence I mined a tiny bit of Ruby for the task.  Ruby had been designed to be an object-oriented Perl, and expectedly it feels very similar to Raku.  To an extend, I was also able to avoid ALGOL-style call do quite a <a href="https://www.codesections.com/blog/raku-lisp-impression">Lisp impression</a>.</p>
<p>When I was looking for a second language to learn after the peak of my CP <em>career</em> in middle school, I was choosing between those with garbage-collection that are most popularly used in <a href="https://www.gnu.org/philosophy/free-sw.html">free software</a> at the time, namely Perl, Python and Ruby.  Perl was ruled out due to my fear of <a href="https://raku-advent.blog/2022/12/20/sigils">sigils</a> and I picked up Python as I didn&#39;t want to be a <a href="https://en.wikipedia.org/wiki/Japanophilia#21st_century">weeaboo</a>. Sometimes I wonder how my side projects would have turned out to be had I chosen differently.</p>
<h2 id="day_six">Day Six</h2>
<p>The <a href="https://adventofcode.com/2022/day/6">sixth problem</a> essentially asked for maintaining a finite queue of English letters until it is distinct.  The most efficient way to do this is employing bit shifting for the FIFO and a bit set for the letters. <a href="https://trong.loang.net/~cnx/cp/commit?id&#61;f82f0b1a08f1">I implemented that</a> literally in the <a href="https://dlang.org/spec/betterc.html">Better C</a> subset of <a href="https://dlang.org">D</a>.</p>
<p>Although the language is around my age and influenced the big names like modern C&#43;&#43;, Swift and Zig,<sup id="fnref:cmp">[5]</sup> its documentation is pretty underwhelming and inconsistent.  For instance, the 128-bit integer type <code>cent</code> is documented as a <a href="https://dlang.org/spec/type.html#basic-data-types">basic data type</a>, however it only exists in the <a href="https://dlang.org/phobos/core_int128.html">core.int128</a> library with more cumbersome usage &#40;and doesn&#39;t work with <code>dmd -betterC</code>&#41;.</p>
<p>Like with Nim, D compilers also don&#39;t allow hyphens in source filenames, so I had to pipe the code to <code>dmd -of&#61;a.out -</code> &#40;the executable name would be randomized otherwise&#41;.</p>
<h2 id="day_seven">Day Seven</h2>
<p>On the first <a href="https://vine.co/v/iM0HnpBebd0">Wednesday</a> of the month of celebration, the <a href="https://adventofcode.com/2022/day/7">problem</a> was parsing <code>cd</code> and <code>ls</code>-like invocation and output to reconstruct a directory tree and do, uh, tree stuff.  <a href="https://janet-lang.org">Janet</a>&#39;s <a href="https://janet-lang.org/docs/peg.html">PEG module</a> was much more <a href="https://trong.loang.net/~cnx/cp/commit?id&#61;38d8920c7d7c">delightful</a> for parsing than regular expression on steroids like Raku&#39;s <a href="https://docs.raku.org/language/grammars">grammar</a>.</p>
<p>Writing imperative S-expressions felt dirty, though it&#39;s IMHO a quite better take than Lua, understandably as it was originally a redesign of <a href="https://fennel-lang.org">Fennel</a>.</p>
<h2 id="day_eight">Day Eight</h2>
<p>The <a href="https://adventofcode.com/2022/day/8">eighth problem</a> could be efficiently solved via dynamic programming on multidimensional arrays so I <a href="https://trong.loang.net/~cnx/cp/commit?id&#61;f8b0528d933f">used</a> Fortran for array programming. There&#39;s not much to say other than that it werkt and, ah yea, dynamic allocation didn&#39;t seem worth the effort so I ended up hardcoding the sizes.</p>
<h2 id="day_nine">Day Nine</h2>
<p>The <a href="https://adventofcode.com/2022/day/9">ninth task</a> was about sparse matrix transformation. Naturally I used hash table in Tcl for this purpose and the <a href="https://trong.loang.net/~cnx/cp/commit?id&#61;cde44cdda55d">solution</a> was straightforward enough. I am planning on extending a video game&#39;s level configuration to be programmable and the top contenders are now Lua/Fennel, Janet and Tcl.  No idea when I&#39;ll get to it, but I&#39;mma keep ya posted.</p>
<h2 id="day_ten">Day Ten</h2>
<p>On <a href="https://adventofcode.com/2022/day/10">day 10</a>, I needed to build a less-than-basic<sup id="fnref:egg">[6]</sup> calculator. I thought using AWK would spice things up a bit, but it actually simplified the <a href="https://trong.loang.net/~cnx/cp/commit?id&#61;5e4395eab495">solution</a>.  Instead of having to read and parse each operation, the script is executed for each input line, even allowing interleaving matching.  Therefore, the behavior specification could be followed closely without any significant effort on adapting the logic for the language.</p>
<p>I used to think of AWK as just a more verbose sed&#40;1&#41;. I was wrong and am glad that I was.  I guess AWK can come in pretty handy for similar real-world usages, such as log processing or moderately complex transformation of textual data.</p>
<h2 id="may_day">May Day</h2>
<p><a href="https://en.wikipedia.org/wiki/Oops&#33;..._I_Did_It_Again_&#40;album&#41;">Oops&#33;… I did it again.</a><sup id="fnref:2021">[2]</sup>  If you thought because I published this right after Christmas it must be a complete advent journal, I have played you for absolute fools&#33;  The later problems were increasingly parsing heavy, and while I still had languages I wanted to try, none left was designed for text processing.  I was also busy in meatspace at the time thus I couldn&#39;t find the time to write byte-level parsers in languages I didn&#39;t know.</p>
<p>I didn&#39;t try really hard nor got really far, but <a href="https://www.youtube.com/watch?v&#61;eVTXPUF4Oz4">in the end</a> maybe the <a href="https://www.youtube.com/watch?v&#61;l7r-R61W1DQ">real treasure</a> was the experiences I had along the way. I suppose the <a href="https://en.wikipedia.org/wiki/Contact_hypothesis">contact hypothesis</a> <em>might</em> be true, at least in this context<a href="https://www.youtube.com/watch?v&#61;M94ii6MVilw">;</a> my prejudice against many languages had been cleared away even after surface-level interactions.  You should probably also give it a try, who knows, it could be much <a href="https://en.wiktionary.org/wiki/gay#Middle_English">gay</a>er than you&#39;d expect&#33;</p>
<table class="fndef" id="fndef:master">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">No, I have not been given any slave.</td>
    </tr>
</table><table class="fndef" id="fndef:2021">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">I know, last year I already quit citing how janky later problems were.</td>
    </tr>
</table><table class="fndef" id="fndef:obj">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">camelCase was popularized by mainstream object oriented languages.</td>
    </tr>
</table><table class="fndef" id="fndef:re">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content">Not really, reading byte-by-byte would also work, just less cool.</td>
    </tr>
</table><table class="fndef" id="fndef:cmp">
    <tr>
        <td class="fndef-backref">[5]</td>
        <td class="fndef-content">I feel underachieved now.</td>
    </tr>
</table><table class="fndef" id="fndef:egg">
    <tr>
        <td class="fndef-backref">[6]</td>
        <td class="fndef-content">No eggs were harmed in the making of the solution.</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/advent@cnx%3E&Subject=Re: Advent of Programming Languages">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/advent@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/advent/comments.xml</wfw:commentRss>
</item>
<item>
  <title>De-Dependency December</title>
  <link>https://cnx.gdn/blog/dedep/index.html</link>
  <guid>https://cnx.gdn/blog/dedep/index.html</guid>
  <description>Call for Participation: De-Dependency December</description>
  <category>fun</category><category>pkg</category>
  <pubDate>Thu, 10 Nov 2022 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="de-dependency_december">De-Dependency December</h1>
<blockquote>
<p>As we mature, the dependency graph matures with us.</p>
</blockquote>
<h2 id="exposition">Exposition</h2>
<p>In the <a href="https://www.youtube.com/watch?v&#61;stChOsejLEQ">occasional fights</a> between system and language packagers, <a href="https://man.sr.ht/~cnx/ipwhl">I&#39;m known to take the downstream camp.</a>  As a user, there are lots of things I take for granted.  I install the stuff I need, occasionally upgrade the system, and everything gets updated. Vulnerability in a library used by multiple programs?  Its patched version gets swapped in within a few hours &#40;given it&#39;s not <a href="https://blogs.gentoo.org/mgorny/2021/02/19/the-modern-packagers-security-nightmare">vendored or pinned</a>&#41;. <a href="https://wiki.debian.org/Hardening">Most</a> <a href="https://fedoraproject.org/wiki/Changes/Harden_All_Packages">distributions</a> <a href="https://wiki.archlinux.org/title/Arch_package_guidelines/Security">even</a> <a href="https://en.opensuse.org/openSUSE:Security_Features">apply</a> <a href="https://wiki.gentoo.org/wiki/Hardened/Toolchain#Changes">hardening</a> <a href="https://nixos.org/manual/nixpkgs/stable#sec-hardening-in-nixpkgs">flags</a> that <a href="https://xeiaso.net/blog/openssl-alarm-fatigue">some bugs aren&#39;t even exploitable in the first place</a>.  They create a <a href="https://www.youtube.com/watch?v&#61;205ODJgAEik">safe place</a> for me to comfortably express myself at work and at home.</p>
<p>Recently on my work computer, I&#39;ve switched to Guix System, which has yet many packages.  Looking into the way to package programs I use and ongoing efforts, I realized the colossal number of transitive dependencies of <a href="https://issues.guix.gnu.org/55903">certain software</a> and the impracticality for a user union &#40;i.e. a distro&#41; to maintain such set of <a href="https://raku-advent.blog/2021/12/06/unix_philosophy_without_leftpad">micro packages</a> in every language.</p>
<h2 id="confrontation">Confrontation</h2>
<p>This gave me a more serious thought on software sustainability.  Such topic often reminds us of energy consumption, modularity, development model, or even style &#40;clean code&#41;.  End-users &#40;including self-hosts&#41;, on the other hand, ask the following questions to decide upon installing and keeping a piece of software:</p>
<ul>
<li><p>Can I <em>trust</em> installing this won&#39;t do anything funny to my machine?</p>
</li>
<li><p>How much <a href="https://xkcd.com/303">effort</a> I need to prevent people from doing funny things to my machine if the software includes <a href="https://heartbleed.com">something that gets on the front page of some magazines</a> tomorrow?</p>
</li>
<li><p>How much of my limited resources will it take to run or <a href="https://ludocode.com/blog/flatpak-is-not-the-future">simply exist</a>?</p>
</li>
</ul>
<p>There are certain intersections in concerns of enterprises and users, however it&#39;s worth noticing that distributions are almost exclusively optimized to cater for the users&#39; need.  Not only they <a href="https://en.wikipedia.org/wiki/Tron">fight for the users</a>, they <em>are</em> the users.  Suppose you don&#39;t want to write yellow-glowing programs, you should <a href="https://drewdevault.com/2021/09/27/Let-distros-do-their-job.html">make the life of downstream package maintainers easier</a>. No, it does not count if you push them to give in to run <a href="https://github.com/NixOS/nixpkgs/blob/master/pkgs/build-support/go/module.nix"><code>go mod vendor</code></a> or <a href="https://github.com/svanderburg/node2nix">download from NPM recursively</a>.</p>
<h2 id="resolution">Resolution</h2>
<p><em>So how do I write software that is easy to package</em>, you may ask. If you followed the articles linked above, you&#39;ve probably already figured that out.  It&#39;s less about what you <em>write</em> and more about what you <em>use</em>.  When someone complains a program is difficult to build from source, certainly it&#39;s not about how hard it is to type, say <code>make install</code>, but acquiring the dependencies for that to run successfully and the result will work.</p>
<p>Lower the number of dependencies will absolutely help.  To put it bluntly, you can&#39;t have a problem with dependencies if there&#39;s none of them. This sounds like reinventing the wheel, but if the use case is common enough, you might find what you need in the standard library.<sup id="fnref:rust">[1]</sup> I&#39;ve been restricting myself from using third-party libraries for new side projects and it actually worked for my most recent ones:</p>
<ul>
<li><p><a href="https://trong.loang.net/phylactery">Phylactery</a>, a static comics web server on Go with <a href="https://en.wikipedia.org/wiki/Comic_book_archive">CBZ</a> parsing and concurrent request handling</p>
</li>
<li><p><a href="https://trong.loang.net/~cnx/fead">Fead</a>, an <a href="https://en.wikipedia.org/wiki/Static_site_generator">SSG</a> plugin in Python for advertising others’ feed with parallel HTTP request, parsing of RSS 2 and Atom and CLI argument parsing</p>
</li>
</ul>
<p>Even for such simple use cases, there are still many libraries in the wild that can handle more data formats, are more convenient to use or more performant.  On the other hand, the amount of maintenance needed to keep the programs safe indefinitely for a user is much lower thanks to the small dependency footprint.</p>
<p>What I&#39;m asking you to give a try in the advent days<sup id="fnref:advent">[2]</sup> is not as drastic. Look through your works, find a library you require for a small portion of its <a href="https://www.youtube.com/watch?v&#61;3Mpyias9ek4">power</a>, or something can be implemented specifically for your project using reasonable effort &#40;w.r.t. the whole codebase&#41;.  This is not just for the sake of maintainability: <a href="https://guide.handmade-seattle.com/c/2021/context-is-everything">being less general, the new implementation can likely outperform the replaced public library</a>.</p>
<p><img src="https://cnx.gdn/assets/outlets.jpg" alt="Multiple types of sockets installed on the same wall" /></p>
<p>In many cases, you will find yourself making use of the standard library. Standards make life much easier, <a href="https://xkcd.com/927">if only</a> people can come up with an agreement.  Or maybe they don&#39;t have to.  Maybe each could choose among the <a href="https://raku-advent.blog/2021/12/11/unix_philosophy_without_leftpad_part2">utilities libraries</a>.  At the end of the day, it&#39;s the total number of packages that can have bugs to be reported upstream and patched that matters.</p>
<p>That being said, please keep an eye on the standard library the same way you &#40;should&#41; watch your other dependencies, just in case what you need is finally added.  Worry not of backward incompatibility, <a href="https://wiki.debian.org/DontBreakDebian#Don.27t_suffer_from_Shiny_New_Stuff_Syndrome">users of LTS systems are content with older versions</a> of your software.</p>
<h2 id="fall_and_catastrophe">Fall and Catastrophe</h2>
<p>Just kidding, I&#39;m offering <a href="https://en.wikipedia.org/wiki/Three-act_structure">answers</a>, not <a href="https://en.wikipedia.org/wiki/Dramatic_structure#Freytag&#39;s_pyramid">tragedies</a>.  Winter is coming, join me in a De-Dependency December and fight for the users&#33;</p>
<table class="fndef" id="fndef:rust">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">Unless you use Rust.</td>
    </tr>
</table><table class="fndef" id="fndef:advent">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">I&#39;m not Christian, but I had fun with <a href="https://adventofcode.com">AoC</a> and <a href="https://breezewiki.com/neopets/wiki/Advent_Calendar">Neopets</a> before.</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/dedep@cnx%3E&Subject=Re: De-Dependency December">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/dedep@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/dedep/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Green Leaf Soup</title>
  <link>https://cnx.gdn/blog/greens/index.html</link>
  <guid>https://cnx.gdn/blog/greens/index.html</guid>
  <description>An easy template for making savory soup from green leaf vegetables</description>
  <category>lyf</category><category>recipe</category>
  <pubDate>Sun, 11 Sep 2022 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="green_leaf_soup">Green Leaf Soup</h1>
<p>At the time of writing, I am sharing a kitchen with around 40 people from all parts of the world.  Very often, someone asks me to share a recipe from my cuisine, and I usually have to decline, blaming the lack of fresh ingredients,<sup id="fnref:fresh">[1]</sup> while I can only afford to shop for groceries weekly.  While most Vietnamese dishes call for fresh meat &#40;I can only buy refrigerated one&#41;, in certain case it doesn&#39;t really matter. One quick dish that could tolerate days-old meat is a soup of green leaf vegetables.</p>
<p>Back when I was still at home, a meal almost always consists of a soup. When we are about to finish a bowl of rice, we mix in the soup to wash all of the gelatinized starch &#40;into our mouth&#41;.  The soup could be anything from boiled vegetable broth to <a href="https://en.wikipedia.org/wiki/Basella_alba">vine spinach</a> and jute soup with crab juice.  In that range of difficulty, I would rate the following recipe somewhere in the lower middle.</p>
<h2 id="ingredients">Ingredients</h2>
<p>For a vegetable soup, of course you need a lot of veggie.  As much as you can eat.  I would recommend at least two handful per serving<sup id="fnref:amount">[2]</sup> of any <a href="https://en.wikipedia.org/wiki/Brassica">Brassica</a> leafs, e.g. mustard greens, spoon cabbage or regular cabbage.<sup id="fnref:alt">[3]</sup>  The greener the plant the less starchy it is and the better it blends with the umami of the meat.</p>
<p>As for the animal product, minced or ground pork is a common choice. Minced chicken, fish or dried shrimp also works, but IMHO beef, lamb or goat could overpower the veggie.  Meat is not the star of the show and should be used moderately, 50 grams<sup id="fnref:imperial">[4]</sup> would be generous. There is no vegan variation of this dish AFAICT, except for reducing to just water, leafs and seasoning, but even a child could cook that without a recipe.</p>
<p>In addition, a shallot is required for searing and <a href="https://en.wikipedia.org/wiki/Fish_sauce#Vietnam">fish sauce</a> for seasoning. It is OK-ish to use onion in place of shallot;<sup id="fnref:onion">[5]</sup> I am not a fan of using soy sauce in this dish though.  Super salt &#40;table salt and MSG 9:1 mix&#41; is a better substitution in case you can&#39;t get your hands on <em>the</em> signature Vietnamese seasoning.</p>
<p>Last but not least, it would not be a soup without water. A cup should be enough to emerge the cooked veggie.</p>
<h2 id="preparation">Preparation</h2>
<p>First, wash and slice the vegetable and throw it in a colander to let the water rinse of.  Next, chop the shallot <em>thinly</em>.</p>
<p>If you bought minced or ground meat, you are done preparing. Otherwise, it&#39;s mincing/grinding time, duh&#33;</p>
<h2 id="cooking">Cooking</h2>
<p>Turn the stovetop to medium high and put on a stainless steel pan or pot. Doesn&#39;t have to stainless steel, anything smooth without a polymer coating would do.  Pour in a touch of cooking oil &#40;or a tiny spoon of lard&#41; and start sautéing the shallot.</p>
<p>As soon as the pot is hot enough, immediately add the meat &#40;don&#39;t wait for the shallot to turn golden brown, the slices are thin enough to be caramelized as the meat is seared&#41;.  You don&#39;t need to stir since we don&#39;t need evenly cook it right now, but don&#39;t let it stick together. Use a spoon or a scraper to break it up and press it down for faster searing.</p>
<p>If you have fish sauce, pour it in after the meat finishes browning to develop even more flavor for a few seconds.  Then, deglaze the pot using water and bring it to a boil.  Throw the leafs into the pot and get the water boiling again.  In case you use salt for seasoning, now is time to sprinkle it in the soup.  Let it cook for another two or three minutes &#40;radiant or thermal conductive coil could be switched off and maintain the heat for that duration&#41; and it&#39;s ready to serve&#33;</p>
<table class="fndef" id="fndef:fresh">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content"><em>Good</em> Vietnamese food I grow up eating are always from the freshest.</td>
    </tr>
</table><table class="fndef" id="fndef:amount">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">From now on, the amount of each ingredient is listed for one serving.</td>
    </tr>
</table><table class="fndef" id="fndef:alt">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">Outside of the genus, <a href="https://en.wikipedia.org/wiki/Sauropus_androgynus">rau ngót</a> is awesome if available.</td>
    </tr>
</table><table class="fndef" id="fndef:imperial">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content">Or about a dozen bullets in eagle and burger unit.</td>
    </tr>
</table><table class="fndef" id="fndef:onion">
    <tr>
        <td class="fndef-backref">[5]</td>
        <td class="fndef-content">Not a whole onion, but around the size of your thumb per serving.</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/greens@cnx%3E&Subject=Re: Green Leaf Soup">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/greens@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/greens/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Comments for Static Sites without JavaScripts</title>
  <link>https://cnx.gdn/blog/reply/index.html</link>
  <guid>https://cnx.gdn/blog/reply/index.html</guid>
  <description>Comments for Static Sites without JavaScript via Emails</description>
  <category>fun</category><category>recipe</category><category>net</category>
  <pubDate>Sun, 09 Jan 2022 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="comments_for_static_sites_without_javascripts">Comments for Static Sites without JavaScripts</h1>
<blockquote>
<p>I&#39;m open for criticism<br />But really, is it any room for criticism?</p>
</blockquote>
<p>Recently, I&#39;ve switched my <a href="https://en.wikipedia.org/wiki/Web_feed">feed</a> reader from <a href="https://newsboat.org">Newsboat</a> to <a href="https://lzone.de/liferea">Liferea</a>. The latter has a GUI and some extra features which make the experience a lot more comfy.  For instance, custom enclosure handling lets me to finally migrate all of my YouTube subscriptions to <a href="https://en.wikipedia.org/wiki/Atom_&#40;Web_standard&#41;">Atom</a> and <em>conveniently</em> browse and watch videos using <a href="https://mpv.io">mpv</a>.  Image support also allows me to directly view web comics.<sup id="fnref:image">[1]</sup>  One of them, <a href="https://themonsterunderthebed.net">The Monster Under the Bed</a>,<sup id="fnref:nsfw">[2]</sup> does not embed the strips in its feed, but it has comments.</p>
<p>Yes, <a href="https://www.rssboard.org/rss-specification">RSS</a> includes support for <code>&lt;comments&gt;</code>, and I was not aware of it until <a href="https://nixnet.social/notice/AEO3fYbuzYCJl85eD2">very recently</a>.  I suppose many other people late to the &#40;web feed&#41; party are neither.  Since the rise of static sites, feeds have regain popularity, even for <a href="https://www.theregister.com/2021/05/20/google_rss_chrome_android">Google to reconsider its direction</a>.  Compare to RSS or Atom, alternatives have the following shortcomings:</p>
<ul>
<li><p><a href="https://en.wikipedia.org/wiki/Usenet">Usenet</a> is generally obsolete to most people.</p>
</li>
<li><p><a href="https://en.wikipedia.org/wiki/Mailing_list">Mailing list</a> messages are immutable.</p>
</li>
<li><p>Fora and social media are silos.<sup id="fnref:silo">[3]</sup></p>
</li>
<li><p>Social media are designed for ephemeral discussions.</p>
</li>
<li><p>Instant messaging is awful for archival.</p>
</li>
</ul>
<p>On the other hand, news feeds are commonly read-only: only a few readers can render comments and even fewer are able to post one.  On the server side, a dynamic server is needed to accept comments.  Traditionally, it&#39;s the same as the system serving the website.  Although this works, it is significantly more costly than a server dedicated to static sites, which scale a lot better.</p>
<p><a href="https://en.wikipedia.org/wiki/Hacker">Hackers</a> have came up with multiple workarounds such as using <a href="https://carlschwan.eu/2020/12/29/adding-comments-to-your-static-blog-with-mastodon">microblogging</a> or <a href="https://cactus.chat">instant messaging</a> to add comments to their static sites, but all require client-side code execution, which is an option for neither RSS nor Atom.  Furthermore, <a href="https://unixsheikh.com/articles/so-called-modern-web-developers-are-the-culprits.html">JavaScript hurts portability and performance</a> on the WWW, hence it should be avoided unless it is absolutely impossible to implement a feature otherwise.  Commenting is not an exception.</p>
<p>Following is my adventure implementing a comment section for this very blog. If you&#39;re also up to the task, I think you should view what I did as an inspiration &#40;rather than a reference&#41; and don&#39;t be afraid to experiment around until satisfaction.</p>
<div class="franklin-toc"><ol><li>Choosing Back-End</li><li>Designing Data Flow</li><li>Implementation<ol><li>Accepting Replies</li><li>Rendering Comments</li><li>Injecting Comments</li></ol></li><li>Moderation</li></ol></div>
<h2 id="choosing_back-end">Choosing Back-End</h2>
<p>As mentioned earlier, static sites or not, there still needs to be a dynamic component to accept incoming replies.  HTTP requests would be the most portable since all netizen obviously have a web browser, but those are what we&#39;re trying to replace here.  What else does everyone has nowadays? Something so common that it can be used to identify people upon service registrations?  Exactly, emails and phone numbers&#33;</p>
<p>OK, Imma stop horsing around.  My back-end of choice would be emails. It&#39;s global, it&#39;s cheap and federated.  Cellular services almost fit the bill, except that they would cost an arm and leg for one to comment around the web everyday via SMS, whose character limit is not facilitating thoughtful discussions either.  As for forum, social medium or instant messaging, no platform has nearly as large of an user base as electronic mails.</p>
<p><img src="https://cnx.gdn/assets/html5-js.png" alt="HTML is often a trojan horse for JavaScript" /></p>
<p>It&#39;s not like any email would fit the comment section though.  Especially not the HTML kind with a few hundred kilobytes of embedded CSS, JS and non-content images.  From the security standpoint alone &#39;tis already a no-go.  A light markup language like Markdown<sup id="fnref:mime">[4]</sup> would be much better.</p>
<p>One great thing about using a mature technology like email is that we have all use cases covered.  Filtering, exporting and parsing emails work out-of-box regardless of one&#39;s provider, <a href="https://en.wikipedia.org/wiki/Email_client">MUA</a> and programming preferences.  I have an SourceHut account with which I can create mailing lists on-demand so I&#39;m using it; however there&#39;s no reason exporting from your private inbox is any more difficult, presuming you have set up <a href="https://drewdevault.com/2021/05/17/aerc-with-mbsync-postfix.html">offline email</a>.</p>
<div class="admonition note"><p class="admonition-title">Tips and tricks</p><p>Speaking of SourceHut, exporting a mailing list archive is rather easy, one could either use the button on the web UI or download from the API. As the operation is not exactly cost-free, the former is protected by a <a href="https://en.wikipedia.org/wiki/Cross-site_request_forgery">CSRF</a> token and the latter by <a href="https://man.sr.ht/meta.sr.ht/oauth.md">OAuth 2.0</a>.  If you are a fellow <a href="https://sr.ht">sr.ht</a> user, you can use <a href="https://man.sr.ht/builds.sr.ht/manifest.md#tasks">acurl</a> on the build service with the URL from the <a href="https://lists.sr.ht/graphql">GraphQL</a> <code>query &#123; me &#123; lists &#123; results &#123; name, archive &#125; &#125; &#125; &#125;</code>.</p>
</div>
<div class="admonition note"><p class="admonition-title">Update</p><p>I stopped paying for sr.ht in May 2024 after years of Sourcehut failing to show any measurable progress towards reaching the beta status. I am now using public-inbox for public, eh, inboxes.</p>
</div>
<h2 id="designing_data_flow">Designing Data Flow</h2>
<p>I promise, this sounds bigger than it really is, but first, let&#39;s have a glance at how static generators work.  Typically, there are three times templating happens:</p>
<ol>
<li><p>Conversion of individual articles into HTML <em>content</em></p>
</li>
<li><p>Inserting each article content in a page template to create a complete HTML document</p>
</li>
<li><p>Inserting multiple HTML contents into one RSS or Atom feed template</p>
</li>
</ol>
<p>At completion, two kinds of output are generated: website and web feed. Similarly, comments have to be rendered for both targets: an HTML comment section for web browsing and a separate RSS feed for each article&#39;s <code>&lt;wfw:commentRss&gt;</code>.<sup id="fnref:wfw">[5]</sup>  Therefore, injections should be done separately at stage 2 and 3.  The overall process of static site generation with email comments is illustrated as follows.</p>
<p><img src="https://cnx.gdn/assets/formbox.svg" alt="Data transformation during generation process" /></p>
<p>For clarity, HTML and RSS input templates for comments and their parent page and web feed are omitted.  Path to each <em>comment feed</em> output being injected in the respective <em>web feed item</em> is also not shown in the figure.</p>
<h2 id="implementation">Implementation</h2>
<p>At the time of writing, this personal website of mine was generated by <a href="https://julialang.org">Julia</a> <a href="https://franklinjl.org">Franklin</a>, who was neither fast<sup id="fnref:speed">[6]</sup> nor <a href="https://github.com/tlienart/Franklin.jl/issues/936">semantic</a>, but was the only one I knew supporting LaTeX prerendering out of the box. Franklin is also rather <a href="https://franklinjl.org/syntax/utils">extendable</a> via Julia functions.</p>
<h3 id="accepting_replies">Accepting Replies</h3>
<p>Let&#39;s start with how each article can be programmatically and uniquely identified.  By default in RSS, a <a href="https://www.rssboard.org/rss-profile#element-channel-item-guid">GUID</a><sup id="fnref:guid">[7]</sup> is the permanent URL of the associated web page.  I am not exactly a creative person, so I mirrored this idea, although I only used the difference between URLs, i.e. minus the scheme, network location and trailing <code>index.html</code> &#40;Franklin always appends it to the target path of any source file that is neither <code>index.md</code> nor <code>index.html</code>&#41;:</p>
<pre><code class="language-julia">dir_url&#40;&#41; &#61; strip&#40;dirname&#40;locvar&#40;:fd_url&#41;&#41;, &#39;/&#39;&#41;
message_id&#40;&#41; &#61; &quot;&#37;3C&#36;&#40;dir_url&#40;&#41;&#41;@cnx&#37;3E&quot;</code></pre>
<p>For maximum portability, threading identification is used in emails&#39; <code>In-Reply-To</code> header, which expects a message ID, which must match <code>&lt;.&#43;@.&#43;&gt;</code>.  Once again, to avoid having to think, I opted for the path difference for the left hand side and my nickname <code>cnx</code> for the right.  The <code>mailto</code> URI could be then be constructed accordingly:</p>
<pre><code class="language-julia">using Printf: @sprintffunction hfun_mailto_comment&#40;&#41;
  @sprintf&#40;&quot;mailto:&#37;s?&#37;s&#61;&#37;s&amp;&#37;s&#61;Re: &#37;s&quot;,
           &quot;cnx.site@loa.loang.net&quot;,
           &quot;In-Reply-To&quot;, message_id&#40;&#41;,
           &quot;Subject&quot;, locvar&#40;:title&#41;&#41;
end</code></pre>
<p>The anchor was then added to the page foot:</p>
<pre><code class="language-html">&lt;a href&#61;&quot;&#123;&#123;mailto_comment&#125;&#125;&quot;
   title&#61;&quot;Reply via email&quot;&gt;&#123;&#123;author&#125;&#125;&lt;/a&gt;</code></pre>
<h3 id="rendering_comments">Rendering Comments</h3>
<p>This is when the fun begins.  Julia&#39;s standard library does not include an email parser, and I doubt your favorite language does either, unless it is named after a British comedy troupe.  Python is often described as <em>batteries included</em>, or at least it used to &#40;seemingly the consensus among current core devs has shifted towards <a href="https://discuss.python.org/t/adopting-recommending-a-toml-parser/4068">favoring third-party libraries</a>&#41;.</p>
<div class="admonition note"><p class="admonition-title">Off-topic rambling</p><p>Standard library inclusion wasn&#39;t really the deal breaker here though. I still needed a Markdown engine and a HTML sanitizer &#40;because Markdown can include HTML&#41;, and AFAICT no stdlib has them.  The read issue was with the lack of Julia packaging on most distributions &#40;apart from Guix&#41;, and most certainly <a href="https://github.com/NixOS/nixpkgs/issues/20649">not on NixOS</a>, my current distro.  For the same reason the idea of rewriting Franklin in Python has been running in my head for a while now.  Python packaging is much more downstream-friendly and unlike Julia compilation overhead is almost non-existent.</p>
</div>
<p>On the other hand, it&#39;s trivial to pipe an external program&#39;s output to Julia, e.g. <code>readchomp&#40;&#96;echo foo bar&#96;&#41;</code> would give you the string &quot;foo bar&quot;.  Thus, the to-be-written <em>comment generator</em> should take &#40;the path to&#41; a mail box, the message ID of the article and a template, and write the result to stdout. Argument parsing is, again, thankfully in Python&#39;s stdlib:</p>
<pre><code class="language-python">from argparse import ArgumentParser
from pathlib import Path
from urllib.parse import unquoteparser &#61; ArgumentParser&#40;&#41;
parser.add_argument&#40;&#39;mbox&#39;&#41;
parser.add_argument&#40;&#39;id&#39;, type&#61;unquote&#41;
parser.add_argument&#40;&#39;template&#39;, type&#61;Path&#41;
args &#61; parser.parse_args&#40;&#41;</code></pre>
<p>I then parsed the <a href="https://datatracker.ietf.org/doc/html/rfc4155">mbox</a> into a mapping indexed by parent message IDs as follows.  They would be HTML-unquoted so that was why I needed to do the same for the input message ID.</p>
<pre><code class="language-python">from collections import defaultdict
from email.utils import parsedate_to_datetime
from mailbox import mboxdate &#61; lambda m: parsedate_to_datetime&#40;m&#91;&#39;Date&#39;&#93;&#41;.date&#40;&#41;
archive &#61; defaultdict&#40;list&#41;
for message in sorted&#40;mbox&#40;args.mbox&#41;, key&#61;date&#41;:
    archive&#91;message&#91;&#39;In-Reply-To&#39;&#93;&#93;.append&#40;message&#41;</code></pre>
<p>As said earlier, arbitrary HTML content is not exactly suitable for comments. However, it is undeniable that HTML emails have taken over the world and compromises must be made: allowing <code>multipart/alternative</code> of both <code>text/plain</code> and <code>text/html</code>.  It is not the only multipart, so are attachments and cryptographic signatures.  Since we are only interested in the plaintext part, it is actually easier done than said to extract it:</p>
<pre><code class="language-python">from bleach import clean, linkify
from markdown import markdowndef get_body&#40;message&#41;:
    if message.is_multipart&#40;&#41;:
        for payload in map&#40;get_body, message.get_payload&#40;&#41;&#41;:
            if payload is not None: return payload
    elif message.get_content_type&#40;&#41; &#61;&#61; &#39;text/plain&#39;:
        body &#61; message.get_payload&#40;decode&#61;True&#41;
        return clean&#40;linkify&#40;body, output_format&#61;&#39;html5&#39;&#41;&#41;,
                     tags&#61;..., protocols&#61;...&#41;
    return None</code></pre>
<p>Now all that&#39;s left is to render that body and relevant headers as an HTML segment or an RSS item.  This is when we revisit the template. Jinja is probably the most popular in Python, thanks to Django and Flask, but its complexity is rather unnecessary.  Instead, I went with the built-in <code>str.format</code>.</p>
<p><img src="https://cnx.gdn/assets/format.jpg" alt="Double braces are brilliant, but I prefer single ones" /></p>
<p>What are templates for, exactly?  Not the complete document, apparently, because that would differs from article to article and increase the complexity for injection.  Neither a single comment, as comments are threaded into trees &#40;or a forest&#41; and their relationship can be useful.  We gotta <a href="https://genius.com/Timbaland-meet-in-tha-middle-lyrics">meet in tha middle</a> and use recursive templates instead, e.g. for nested comments:</p>
<pre><code class="language-html">&lt;div class&#61;comment&gt;
  ...
  &#123;children&#125;
&lt;/div&gt;</code></pre>
<p>To render linear comments, such as for <code>&lt;wfw:commentRss&gt;</code>, simply move the children out of the item as follows.</p>
<pre><code class="language-xml">&lt;item&gt;
  ...
&lt;/item&gt;
&#123;children&#125;</code></pre>
<p>The rest substitutions are mostly just extracted from the email&#39;s headers. Another bit that needs some extra decisions, though, is the parameters for the <code>mailto</code> URI to reply to each comment:</p>
<ul>
<li><p><code>In-Reply-To</code> set to current <code>Message-Id</code></p>
</li>
<li><p><code>Cc</code> set to current <code>Reply-To</code> &#40;if exists&#41; or <code>From</code></p>
</li>
<li><p><code>Subject</code> is inherited, with <code>Re:</code> prepended if missing</p>
</li>
</ul>
<p>This is getting boring with a lot of trivial code, so I&#39;ll leave you with a pointer to the completed script named <a href="https://trong.loang.net/~cnx/formbox">formbox</a> and move on to more interesting stuff.</p>
<h3 id="injecting_comments">Injecting Comments</h3>
<p>Inserting HTML comment sections is pretty simple.  First I wrote a simple Julia function <code>render_comments</code> calling <code>formbox</code> under the hood, then</p>
<pre><code class="language-julia">hfun_comments_rendered&#40;&#41; &#61; render_comments&#40;&quot;comment.html&quot;&#41;</code></pre>
<p><code>comments_rendered</code> is then injected below the article.  For RSS, it took an extra steps:</p>
<ol>
<li><p>Insert <code>render_comments&#40;&quot;comment.xml&quot;&#41;</code> to the comment feed template <code>comments.xml</code> &#40;notice they are two different templates&#41; and write it next to the article&#39;s output <code>index.html</code></p>
</li>
<li><p>Insert the path of the written comment feed to the <code>&lt;wfw:commentRss&gt;</code> tag in the article&#39;s feed item</p>
</li>
</ol>
<p>That&#39;s it&#33;</p>
<h2 id="moderation">Moderation</h2>
<p>I don&#39;t want a <em>Terms of Services</em> page, it&#39;d feel too corporate for my <em>personal</em> website, so I will list the rules here:</p>
<ol>
<li><p>Please be excellent to each other.  Disagreements are okay, personal insults are not.</p>
</li>
<li><p>Stay on topic.  If you want to publicly discuss with me about something else, start a new thread on a <a href="https://loa.loang.net/cnx.misc">mailing list</a> or reach me via social media.</p>
</li>
<li><p><a href="https://useplaintext.email">Use plaintext emails</a> and do not top post.  Markdown inline markups, block quotes, lists and code blocks are supported.</p>
</li>
<li><p>Comments are implied to be under <a href="https://creativecommons.org/licenses/by-sa/4.0">CC BY-SA 4.0</a> unless declared otherwise.</p>
</li>
<li><p>I reserve the right to remove any comment I don&#39;t like. I generally don&#39;t delete comments, but if you want to exercise your freedom of speech, publish it yourself.</p>
</li>
<li><p>I do not warrant the availability of the comments either. I will try my best but one day all comments may just disappear, just like this website itself.  Archive what you deem important.</p>
</li>
<li><p>These rules are subject to change according to my personal liking without notice.</p>
</li>
</ol>
<p>Replies will only be rendered on the website and feed after I see them, so please expect a delay of at least 24 hours.  If you are eager to reply to each other, subscribe to the <a href="https://loa.loang.net/cnx.site">site&#39;s mailing list</a> instead.</p>
<table class="fndef" id="fndef:image">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">TBF there are image preview scripts in Newsboat&#39;s <a href="https://drewdevault.com/2020/06/06/Add-a-contrib-directory.html">contrib</a>.</td>
    </tr>
</table><table class="fndef" id="fndef:nsfw">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">Content warning: occasionally NSFW</td>
    </tr>
</table><table class="fndef" id="fndef:silo">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">Federation is getting there for social media; not so much for fora.</td>
    </tr>
</table><table class="fndef" id="fndef:mime">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content">But don&#39;t use <a href="https://blog.brixit.nl/markdown-email">text/markdown</a> for your emails.</td>
    </tr>
</table><table class="fndef" id="fndef:wfw">
    <tr>
        <td class="fndef-backref">[5]</td>
        <td class="fndef-content">Unfortunately there&#39;s no equivalence for Atom.</td>
    </tr>
</table><table class="fndef" id="fndef:speed">
    <tr>
        <td class="fndef-backref">[6]</td>
        <td class="fndef-content">Over 30 seconds to generate a few hundred kB of web pages.</td>
    </tr>
</table><table class="fndef" id="fndef:guid">
    <tr>
        <td class="fndef-backref">[7]</td>
        <td class="fndef-content">Not to be confused with the micro soft hijacked term for <a href="https://en.wikipedia.org/wiki/Universally_unique_identifier">UUID</a>.</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/reply@cnx%3E&Subject=Re: Comments for Static Sites without JavaScripts">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/reply@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/reply/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Generic Homemade Ham</title>
  <link>https://cnx.gdn/blog/gotham/index.html</link>
  <guid>https://cnx.gdn/blog/gotham/index.html</guid>
  <description>An easy template for making uncured ham or similar brined pork</description>
  <category>lyf</category><category>recipe</category>
  <pubDate>Fri, 19 Nov 2021 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="generic_homemade_ham">Generic Homemade Ham</h1>
<p>Where I&#39;m from, hams are stupid expensive due to the lack of demand. This is unacceptable because I <em>love</em> hams&#33;<sup id="fnref:hamm">[1]</sup>  After years of not tasting even a single slice, I decided for myself to make some, and noting down what works and what doesn&#39;t.</p>
<p>Unlike other stuff you usually find on the interweb, the following recipe will not require any fancy equipment,<sup id="fnref:equipment">[2]</sup> chemical<sup id="fnref:chemical">[3]</sup> or quantities that &#40;should&#41; only appear in a math textbook.  It will also try to be flexible, so that you can be free to experiment with whatever you feel like that day, while knowing for sure you&#39;ll still end up with something at least remotely resemble a piece of ham.</p>
<h2 id="brining">Brining</h2>
<p>Making ham, like any other food, comprises of only two steps: preparing and cooking.  Brining not only makes the meat salty<sup id="fnref:self">[4]</sup> but also enhances its tenderness by braking down the proteins.</p>
<p>The most important ingredients for this process are meat, salt and sugar. As for the meat, it&#39;s preferably from a pig&#39;s thigh, but anything with a similar texture will do.  You do want a cut with parallel muscles to minimize the amount of silver skin and tendon though, plus it will have better presentation.  As always, intramuscular fat is a delicious cherry on top, but not too crucial in this case.  On the other hand, any kind of salt and sugar would do.  Personally I use sea salt and brown sugar because they are the cheapest to be found locally, whilst they add some extra flavors and minerals.</p>
<h3 id="dry">Dry</h3>
<p>Dry brining is only suitable for &#40;family-&#41;serving-size cuts of meat, somewhere from 200 to 500 grams.  Anything larger would have troubles absorbing the seasoning.  Otherwise, cover the meat in coarse salt and sugar and leave it in the fridge from a few hours to overnight, depending on its mass.</p>
<p>How much seasoning?  Be generous, but you&#39;d want to still be able to see the meat underneath.  I don&#39;t think you can&#39;t overseason it, just remember to rinse off the remaining rub before cooking.  As for the ratio, I like to twice as much salt as sugar, but I&#39;ve seen people doing 1:1 or even 1:2.</p>
<h3 id="wet">Wet</h3>
<p>The brine formula I&#39;m about to describe is heavily influenced from <a href="https://www.youtube.com/watch?v&#61;5fm3lNM5vV4">Mike G&#39;s recipe</a>, which is also uncured ham.  First, pour enough water to submerge the meat in a pot &#40;no, don&#39;t put the meat in the pot&#41; and heat it up.  If you have a fairly fitting container, the amount is close to the mass of the meat itself.</p>
<p>Then, add 5&#37; salt, 3&#37; sugar, and whatever spices can go well with your future ham.  I usually use a few bay leaves, some thyme and crushed peppercorn, but any aromatic, fresh or dry, should work. You don&#39;t have to be exact with the amount of seasoning either: if you don&#39;t have a scale, measure with a spoon and be generous.  Due to the lack of nitrate, the brining shouldn&#39;t occur for more than a few days and the more concentrated the solution, the faster the absorption.</p>
<p>Let the brine cool down, pour it in a container, drown the meat<sup id="fnref:cereal">[5]</sup> &#40;use a weight if necessary&#41; and put it in the fridge.  A cut of a few hundred grams should take around 24 hours.</p>
<h2 id="cooking">Cooking</h2>
<p>After taking the meat out of the fridge and wash it lightly, wait around an hour for it to reach room temperature.  If you don&#39;t have paper towel, place it on a rack or an elevated plane to dry off the surface.</p>
<p>Before cooking, I like to rub a few other extra spices on my meat. My favorite are smoked paprika &#40;for the smoky flavor&#41;, garlic powder, freshly grounded black pepper and perhaps some nutmeg.</p>
<p>From here, it&#39;s similar to cooking a steak: you&#39;d want it in an environment close to the target temperature, which is around 68°C, or 63°C if pork in your area is heavily regulated.  The closer it is, the smaller the difference between the center and the outer layers may be, i.e. you&#39;ll less likely to overcook the latter.  There are three ways<sup id="fnref:threesome">[6]</sup> to do this indoor: sous vide, pan-frying and oven-roasting.  If you have a sous vide machine, I&#39;d assume you wouldn&#39;t need my instructions, so I will focus on the other two methods.</p>
<h3 id="pan-frying">Pan-frying</h3>
<p>First, rub a touch of cooking oil<sup id="fnref:oil">[7]</sup> all over your meat, then turn on the stove to the lowest-possible heat and place the pan and the meat on it.  It should take 30 to 40 minutes to reach to desired temperature, depending on your stove.  You can use your finger or a chopstick to poke on the meat: if it feels raw it&#39;s probably raw, if it&#39;s solid it&#39;s overcooked; you&#39;d want it bouncy, right before it stops being so.  Yes, it&#39;s a lot of trial and error and unnecessarily stressful, just get a thermometer, especially the one you can stick in for the entire process.</p>
<p>It is not compulsory to sear a ham, but I&#39;m addicted to the <a href="https://en.wikipedia.org/wiki/Maillard_reaction">Maillard reaction</a> so Imma do it anyway.  You can sear before or after cooking, I usually do the latter &#40;reverse searing&#41; because it seems to make more sense. Move the meat to a temporary plate and wipe the pan clean.  Turn the stove up to medium-high and wait for it to get hot.</p>
<p>If your meat does not look like it can fit it a body building contest, coat it with little more oil, then drop it on the pan.  Rotate it every 30 seconds until the whole surface area is golden brown, then transport it back to the plate for resting until you can comfortably touch it before slicing. Serve with yellow mustard.</p>
<h3 id="roasting">Roasting</h3>
<p>If you have an oven, place the meat on its rack and turn it down to lowest heat &#40;mine is 100°C&#41;.  In this method, a thermometer is also compulsory to monitor the meat inner temperature, which should take around 80 minutes to raise to the target one.  I suggest bisecting the checking intervals, e.g. check after 40 minutes, then 20, and so on.</p>
<p>If you&#39;re worried about the wasted energy, you can cut some carrots, potatoes, tomatoes and/or onions &#40;anything high in carbs, really&#41; in half and throw them in the oven.  After taking the meat out, turn the oven up to highest and you&#39;ll have some beautifully caramelized side dishes.</p>
<p>The oven I have at home is not powerful enough for searing the meat &#40;quickly&#41; so I usually turn to the pan instead.</p>
<h3 id="slow_cooking_bonus">Slow cooking &#40;bonus&#41;</h3>
<p>This is a bonus because I could never make a ham out of it, but pulled pork. On the other hand, it&#39;s so tender that you won&#39;t be able to slice and needs much less attention.  Since we won&#39;t sear the meat, it&#39;s a good idea to use a binding like mustard to stick even more rubbing spices on the surface.</p>
<p>After rubbing, touch the bottom of the slow cooker with a bit a oil to avoid sticking, drop the bay leafs from the brine on it and place the meat on top.  Cook <em>low</em> from six to eight hours, then using forks or chopsticks separate the muscles from each other.  You can serve immediately or let it cook a bit more after pulling.</p>
<table class="fndef" id="fndef:hamm">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">Especially <a href="https://www.youtube.com/watch?v&#61;IiLJsOsRKUI">Jon Hamm&#39;s John Ham</a>.</td>
    </tr>
</table><table class="fndef" id="fndef:equipment">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">Ain&#39;t nobody got at smoker at home.</td>
    </tr>
</table><table class="fndef" id="fndef:chemical">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">Where can I get nitrates?  A chemistry lab?</td>
    </tr>
</table><table class="fndef" id="fndef:self">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content">Like yours truly.</td>
    </tr>
</table><table class="fndef" id="fndef:cereal">
    <tr>
        <td class="fndef-backref">[5]</td>
        <td class="fndef-content">Or the other way around, it&#39;s not cereal.</td>
    </tr>
</table><table class="fndef" id="fndef:threesome">
    <tr>
        <td class="fndef-backref">[6]</td>
        <td class="fndef-content">Nice&#33;</td>
    </tr>
</table><table class="fndef" id="fndef:oil">
    <tr>
        <td class="fndef-backref">[7]</td>
        <td class="fndef-content">One with smoking point above 170°C.</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/gotham@cnx%3E&Subject=Re: Generic Homemade Ham">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/gotham@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/gotham/comments.xml</wfw:commentRss>
</item>
<item>
  <title>NixOS on Btrfs&#43;tmpfs</title>
  <link>https://cnx.gdn/blog/butter/index.html</link>
  <guid>https://cnx.gdn/blog/butter/index.html</guid>
  <description>How I reinstalled NixOS on Btrfs with an amnesiac root        and backed up my data</description>
  <category>fun</category><category>recipe</category><category>nix</category>
  <pubDate>Sun, 14 Nov 2021 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="nixos_on_btrfstmpfs">NixOS on Btrfs&#43;tmpfs</h1>
<p>In 2018, dad bought me a new laptop to replace the good ole Compaq nx7010 whose screen unfortunately got infected by some sort of microbe and dieded shortly afterwards.  The new one, whilst having a considerably worse build quality &#40;like all other late-2010s ones when compared to mid-2000s models&#41;, had a dozen times as much storage: a 250 GB M.2 SSD and a 500 GB SATA HDD.</p>
<p>My data hoarding habit has grown exponentially ever since.  Initially, I used to back up the data from the SSD to the HDD but after a few years, I ran out of space and decided to get some more storage.  Instead of buying a portable hard disk like a normal person would, I went for an SATA SSD, as it was rather difficult to find a 7200 rpm 2.5-inch<sup id="fnref:metric">[1]</sup> HDD in the market at the time.</p>
<p>I then asked my father for a spare SATA-to-USB case &#40;he switched to using a dock a while ago, and like other dads, nothing is ever thrown away&#41; and prepared to swap the drives.  As cloning the data would have been too easy, I decided to <em>spice things up</em> by reinstalling the OS.  Back then I was dual-booting Debian and NixOS, but the former had hardly been ever booted for months so it was time to let it go:</p>
<p><img src="https://cnx.gdn/assets/let-it-go.png" alt="Elsa rolling on the floor crying" /></p>
<p>In addition, I wanted to hop on the new and shinny<sup id="fnref:new">[2]</sup> train of Btrfs. It has compression, snapshots and subvolumes, what&#39;s not to love?  Let&#39;s replace something I&#39;d been using for nearly a decade with a file system I had absolutely zero experience with, what could possibly go wrong, right?</p>
<div class="franklin-toc"><ol><li>Reinstallation<ol><li>Preparation</li><li>Partitioning</li><li>Configuration</li><li>Installation</li><li>Profits</li></ol></li><li>Backup<ol><li>Initialization</li><li>Repetition</li></ol></li></ol></div>
<h2 id="reinstallation">Reinstallation</h2>
<p>I was going to reinstall NixOS with an ephemeral root, which had been covered to death in the following brilliant resources:</p>
<ul>
<li><p><a href="https://grahamc.com/blog/erase-your-darlings">Erase your darlings: immutable infrastructure for mutable systems</a></p>
</li>
<li><p><a href="https://elis.nu/blog/2020/05/nixos-tmpfs-as-root">NixOS ❄: tmpfs as root</a></p>
</li>
<li><p><a href="https://github.com/nix-community/impermanence">Nix community&#39;s impermanence modules</a></p>
</li>
<li><p><a href="https://christine.website/blog/paranoid-nixos-2021-07-18">Paranoid NixOS Setup</a></p>
</li>
</ul>
<p>The only twist here is that I was using Btrfs instead of ZFS or ext4 like in other guides.  This choice would influence how to back up in the later section.</p>
<h3 id="preparation">Preparation</h3>
<p>First of all, I temporarily copied data to the SATA SSD from the M.2, including <a href="https://trong.loang.net/~cnx/dotfiles/tree/nix">my Nix configurations</a>.  Using either <code>cp</code> or <code>rsync</code> didn&#39;t seem to have any effect on the performance, and in the mean time I also went ahead and grabbed a <a href="https://channels.nixos.org/nixos-unstable">NixOS unstable live image</a> and <code>dd</code>&#39;ed it to a flash drive.  As I&#39;m tracking unstable, installing from the same version would allowed me to skip switching the channel and a lot of downloading.</p>
<h3 id="partitioning">Partitioning</h3>
<p>After booting up the live image, I opened up a root shell with <code>sudo -i</code>. As expected, <code>fdisk</code> reports the M.2 SSD as <code>/dev/nvme0n1</code>.  Paranoid as always, I decided to give the EFI system partition a whole gibibyte, swap eight to match memory<sup id="fnref:memory">[3]</sup> and the rest as a single chonky Btrfs partition:</p>
<pre><code class="language-sh">parted /dev/nvme0n1 -- mklabel gpt
parted /dev/nvme0n1 -- mkpart ESP fat32 1MiB 1GiB
parted /dev/nvme0n1 -- set 1 boot on
mkfs.vfat /dev/nvme0n1p1parted /dev/nvme0n1 -- mkpart Swap linux-swap 1GiB 9GiB
mkswap -L Swap /dev/nvme0n1p2
swapon /dev/nvme0n1p2parted /dev/nvme0n1 -- mkpart primary 9GiB 100&#37;
mkfs.btrfs -L Butter /dev/nvme0n1p3</code></pre>
<p>As I typed this, I realized that I should have set up encryption for the last partition so I would probably need to reinstall in the near future to fix this mistake.  Anyway, with the target system&#39;s root mounted as tmpfs, I would need to persist <code>/nix</code> &#40;obviously&#41;, <code>/etc</code> &#40;mostly for authentication and other secret stuff not included in <code>configuration.nix</code> that I was too lazy to opt in individually&#41;, <code>/var/log</code>, <code>/root</code> and <code>/home</code>:</p>
<pre><code class="language-sh">mount /dev/nvme0n1p3 /mnt
btrfs subvolume create /mnt/nix
btrfs subvolume create /mnt/etc
btrfs subvolume create /mnt/log
btrfs subvolume create /mnt/root
btrfs subvolume create /mnt/home
umount /mnt</code></pre>
<p>Most subvolumes can be mounted with <code>noatime</code>, except for <code>/home</code> where I frequently need to sort files by modification time.  All of them should have forced compression though:</p>
<pre><code class="language-sh">mount -t tmpfs -o mode&#61;755 none /mnt
mkdir -p /mnt/&#123;boot,nix,etc,var/log,root,home&#125;
mount /dev/nvme0n1p1 /mnt/boot
mount -o subvol&#61;nix,compress-force&#61;zstd,noatime /dev/nvme0n1p3 /mnt/nix
mount -o subvol&#61;etc,compress-force&#61;zstd,noatime /dev/nvme0n1p3 /mnt/etc
mount -o subvol&#61;log,compress-force&#61;zstd,noatime /dev/nvme0n1p3 /mnt/var/log
mount -o subvol&#61;root,compress-force&#61;zstd,noatime /dev/nvme0n1p3 /mnt/root
mount -o subvol&#61;home,compress-force&#61;zstd /dev/nvme0n1p3 /mnt/home</code></pre>
<h3 id="configuration">Configuration</h3>
<p>With everything mounted, <code>nixos-generate-config --root /mnt</code> could be run to generate a basic configuration.  But wait, didn&#39;t I say something about my dot files?  That&#39;s correct, but it&#39;s not easy to handcraft the <code>hardware-configuration.nix</code>.  After making sure all are mounted with the right options and <code>services.fstrim.enable</code> is <code>true</code>, I copied other configuration files to <code>/etc/nixos</code> and finished this step.</p>
<h3 id="installation">Installation</h3>
<p>NixOS installation is as simple as running <code>nixos-install</code>.  But my job was not done after setting the root password and rebooting into the new system. It was working, but not functional.  There was nothing meaningful for me to do on it, so I had to log in &#40;as root&#41;, <code>passwd</code>&#39;ed the user and copied the home folder back from the temporary drive.</p>
<p>After freeing the new SATA SSD, I also filled it with butter.  Yes, all the way, no GPT, no MBR, just Btrfs, whose subvolumes were used in place of partitions:</p>
<pre><code class="language-sh">mkfs.btrfs -f -L Fly /dev/sdb
mkdir -p /mnt
mount /dev/sdb /mnt
btrfs subvolume create /mnt/movies</code></pre>
<p>At that time the only disposable data I had were my movies collection. The HDD also contained other data but they were rebalanced at <code>/home</code> &#40;on the M.2&#41;.  After swapping the SATA SSD inside the laptop, I logged in as the normal user and get the exact same environment before the reinstallation.</p>
<h3 id="profits">Profits</h3>
<p>Thanks to subvolumes and compression, the free spaces were no longer fragmented and I think I gained like 100 GB &#40;not counting the old Debian&#39;s root&#41;.  Backup would also be less painful with Btrfs snapshots &#40;instead of plain <code>rsync</code> like I used to&#41; as shown as follows.</p>
<h2 id="backup">Backup</h2>
<p>With all data migrated, the HDD could be used for backing up.  First, some legacy data I no longer access were moved there, then I started to back up my <code>/home</code> partition:</p>
<h3 id="initialization">Initialization</h3>
<p>Having learned my lesson, I did not forget to set up <a href="https://gitlab.com/cryptsetup/cryptsetup">LUKS</a> this time:</p>
<pre><code class="language-sh">cryptsetup luksFormat /dev/sdb
cryptsetup luksOpen /dev/sdb backup</code></pre>
<p>To make use of snapshots, the backup drive gotta be Btrfs as well. The compression level was turned up to 14 this time &#40;default was 3&#41;:</p>
<pre><code class="language-sh">mkfs.btrfs -L Backup /dev/mapper/backup
mkdir /backup
mount -o noatime,compress-force&#61;zstd:14 /dev/mapper/backup /backup</code></pre>
<p>Following <a href="https://btrfs.wiki.kernel.org/index.php/Incremental_Backup">Btrfs Wiki</a>, I made the first <code>/home</code> snapshot and sent it to the backup drive:</p>
<pre><code class="language-sh">btrfs subvolume create /backup/home
today&#61;&#36;&#40;date --iso-8601&#41;
btrfs subvolume snapshot -r /home /home/&#36;today
sync
btrfs send /home/&#36;today | btrfs receive /backup/home
sync</code></pre>
<h3 id="repetition">Repetition</h3>
<p>For next backups, I also mounted the drive and created a snapshot:</p>
<pre><code class="language-sh">cryptsetup luksOpen /dev/sdb backup
mkdir -p /backup
mount -o noatime,compress-force&#61;zstd:14 /dev/mapper/backup /backup
today&#61;&#36;&#40;date --iso-8601&#41;
btrfs subvolume snapshot -r /home /home/&#36;today
sync</code></pre>
<p>Say the latest snapshot was on the <code>&#36;previous</code> day, I only needed to send the difference between the old and new backup.  Afterwards, it is safe to delete the local <code>&#36;previous</code> snapshot to save some space.</p>
<pre><code class="language-sh">btrfs send -p /home/&#36;previous /home/&#36;today | btrfs receive /backup/home
btrfs subvolume delete /home/&#36;previous
sync</code></pre>
<p>Finally, unmount the drive and close the LUKS volume:</p>
<pre><code class="language-sh">umount /backup
cryptsetup luksClose backup</code></pre>
<p>Is this more complicated than good ole <code>rsync</code>?  Yes.  Is it safer?  Also yes, thanks to copy-on-write.  Would I bother using one of the tools suggested in the wiki?  Probably not, I&#39;ve already documented everything in this article in case I forget anything.</p>
<table class="fndef" id="fndef:metric">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">63.5 mm for those outside of the land of guns and burgers</td>
    </tr>
</table><table class="fndef" id="fndef:new">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">OK, maybe not new, but certainly shinny</td>
    </tr>
</table><table class="fndef" id="fndef:memory">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">Slightly larger since some of the memory is dedicated to graphics</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/butter@cnx%3E&Subject=Re: NixOS on Btrfs&#43;tmpfs">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/butter@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/butter/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Writing a Clipboard Manager</title>
  <link>https://cnx.gdn/blog/threa/index.html</link>
  <guid>https://cnx.gdn/blog/threa/index.html</guid>
  <description>Raku&#39;s concision demonstrated in form of a tutorial</description>
  <category>fun</category><category>recipe</category><category>clipboard</category>
  <pubDate>Sat, 03 Jul 2021 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="writing_a_clipboard_manager">Writing a Clipboard Manager</h1>
<div class="admonition note"><p class="admonition-title">A word of protest</p><p>This was intended to be presented in <a href="https://conf.raku.org">The Raku Conference</a>, however the organizers insisted on using <a href="https://stallman.org/zoom.html">Zoom</a> and <a href="https://stallman.org/skype.html">Skype</a>, which are privacy invasive platforms running on proprietary software and shadily managed.</p>
</div>
<div class="franklin-toc"><ol><li>Motivation</li><li>Inspirations and Design</li><li>Daemon Implementation<ol><li>Reading Inputs</li><li>Cache Directory Setup</li><li>Comparing and Saving Selections</li><li>Command-Line Interface</li></ol></li><li>Client Implementation<ol><li>Back-End</li><li>Front-End</li></ol></li><li>Conclusion</li></ol></div>
<h2 id="motivation">Motivation</h2>
<p>Clipboard management is very important to my workflow.  To me, a clipboard manager is useful in two ways:</p>
<ol>
<li><p>It extends my &#40;rather poor&#41; temporary mundane memory by caching a few dozens of most recent selections.</p>
</li>
<li><p>It synchronizes clipboard and primary selections. Since some programs only support one kind of selection, this is particularly useful.</p>
</li>
</ol>
<p>For the first point, I have to be able to choose from the history by pressing a few keystrokes.  Having to touch the mouse during writing sessions is unacceptable.  The menu dropping down from the systray is also undesirable because I have a multi-monitor setup.  This narrows down to only one plausible option: <a href="https://launchpad.net/diodon">Diodon</a>, which I having been using on Debian for at least two years.  However, as I was migrating to NixOS earlier last month, <a href="https://github.com/NixOS/nixpkgs/pull/126190">I was unable to package it for Nix</a>.</p>
<p>Naturally, I went looking for <a href="https://search.nixos.org/packages?query&#61;clip">alternatives</a>, most of which I had tried before and did not satisfy my requirements.  <a href="https://github.com/cdown/clipmenu">clipmenu</a> got my attention however: it was made to work with dmenu&#40;-compliant launchers&#41;, which I had a rather nice experience with in <a href="https://sxmo.org">Sxmo</a> on my <a href="https://www.pine64.org/pinephone">PinePhone</a>. However, I use <a href="https://awesomewm.org">awesome</a> on my workstation and its widget toolkit covers my launcher and menu need perfectly.  I don&#39;t need <a href="https://tools.suckless.org/dmenu">dmenu</a> and do not wish to spend time configuring and theming it.  Plus, the architecture of dmenu scripts and awesome widgets vastly differs: while awesome executes the input programs, dmenu is called from the scripts.</p>
<h2 id="inspirations_and_design">Inspirations and Design</h2>
<p>As even the most plausible candidate is not a suitable replacement, I would need to write my own clipboard manager.  clipmenu is not really a good base though because it&#39;s written in shell script, something I ain&#39;t fluent in.<sup id="fnref:1">[1]</sup>  Its idea is brilliant however:</p>
<blockquote>
<ol>
<li><p><code>clipmenud</code> uses <code>clipnotify</code> to wait for new clipboard events.</p>
</li>
<li><p>If <code>clipmenud</code> detects changes to the clipboard contents, it writes them out to the cache directory and an index using a hash as the filename.</p>
</li>
</ol>
</blockquote>
<p>I later translated <a href="https://github.com/cdown/clipnotify">clipnotify</a> to <a href="https://ziglang.org">Zig</a><sup id="fnref:2">[2]</sup> and called it <a href="https://trong.loang.net/~cnx/clipbuzz">clipbuzz</a>.<sup id="fnref:3">[3]</sup> From clipbuzz&#39;s usage,</p>
<pre><code class="language-sh">while clipbuzz
do # something with xclip or xsel
done</code></pre>
<p>and this is exactly how yet another clipboard manager was written, but before we get there, let&#39;s talk about this article&#39;s sponsor&#33;</p>
<p>I&#39;m kidding d-; though we cannot jump into the implementation just yet: we only resolved the first point out of two.  How about the data structure? Hashing sounds like overengineering in this case: nobody needs more than a few dozen entries<sup id="fnref:4">[4]</sup> and hashes are not very memorable.  Printable characters can serve much better as indices.</p>
<p>What?  What happens when we run out of them?  We reuse/recycle them&#33;<sup id="fnref:5">[5]</sup> They would also fit within one single line, heck, we just store all of them in order inside a file and rotate each time there&#39;s a new selection. Picking would just be moving a char to the beginning.  The entire cache directory can just look something like this:</p>
<pre><code class="language-console">&#36; ls &#36;XDG_CACHE_HOME/&#36;project
order
R
A
K
U</code></pre>
<p>Wait, is that a sign?  We must use <a href="https://raku.org">Raku</a> to implement <code>&#36;project</code> then… Speaking of <code>&#36;project</code>, I planned to use it with awesome and <a href="https://vicious.rtfd.io">vicious</a> so let&#39;s call it something brutal, like a <em>cutting board</em>, which is <em>thớt</em> in Vietnamese, an Internet slang for <em>thread</em>.  Cool, now we have the daemon name, and conventionally the client shall be <em>threac</em>, or <em>threa client</em>.</p>
<h2 id="daemon_implementation">Daemon Implementation</h2>
<h3 id="reading_inputs">Reading Inputs</h3>
<p>Raku was chosen<sup id="fnref:6">[6]</sup> for the ease of text manipulation and seamless interfacing with external programs.  I learned it quite a while ago and has always been waiting for a chance to do something more practical with it, other than competitive programming which isn&#39;t a good fit due to Rakudo&#39;s poor performance. In Raku, the snippet from clipbuzz&#39;s README becomes:</p>
<pre><code class="language-sh">while run &#39;clipbuzz&#39; &#123;
    # do something with xclip or xsel
&#125;</code></pre>
<p>Out of all languages I know, this is by far the simplest way to <a href="https://docs.raku.org/routine/run">run</a> an external program.  Most would require one to import something or do something with the call&#39;s return value, and don&#39;t even get me start on POSIX <em>fork</em> and <em>exec</em> model.</p>
<p>OK, now what are we gonna do with <code>xclip</code>?  One obvious thing would be to read the current selection.  Raku got you covered, fam:</p>
<pre><code class="language-sh">my &#36;selection &#61; qx/xclip -out/;</code></pre>
<p>Remember when I said Raku can seamlessly interact with external programs? <a href="https://docs.raku.org/syntax/qx">qx</a> is how you capture their standard output, it is really that simple. But wait, which selection is that?  No worries, <code>xclip</code> supports both primary and clipboard:</p>
<pre><code class="language-sh">my &#36;primary &#61; qx/xclip -out -selection primary/;
my &#36;clipbroad &#61; qx/xclip -out -selection clipboard/;</code></pre>
<h3 id="cache_directory_setup">Cache Directory Setup</h3>
<p>This is when we write those selection down for later use, right? Well, we need to figure out where to save them first.  According to <a href="https://specifications.freedesktop.org/basedir-spec/latest/ar01s03.html">XDG Base Directory Specification</a>, <code>&#36;XDG_CACHE_HOME</code> shall falls back to <code>&#36;HOME/.cache</code>:</p>
<pre><code class="language-sh">my &#36;XDG_CACHE_HOME &#61; &#37;*ENV&lt;XDG_CACHE_HOME&gt; // path &#36;*HOME / &#39;.cache&#39;:;</code></pre>
<p>For convenience purposes, I defined the <code>/</code> operator as an alias for path concatination:</p>
<pre><code class="language-sh">multi sub infix:&lt;/&gt;&#40;&#36;parent, &#36;child&#41; &#123; add &#36;parent: &#36;child &#125;</code></pre>
<p>With <code>&#36;XDG_CACHE_HOME</code> defined, we can prepare the base directory as follows:</p>
<pre><code class="language-sh">my &#36;base &#61; &#36;XDG_CACHE_HOME.IO / &#39;threa&#39;;
mkdir &#36;base: unless &#36;base.e;
die &quot;thread: &#36;base: File exists&quot; when &#36;base.f;</code></pre>
<p>As <a href="https://vrurg.github.io/2021/06/16/article-on-roles">a wise man once said</a>,</p>
<blockquote>
<p>As it often happens, writing an article ends up with a bug found in Rakudo.</p>
</blockquote>
<p>In this case, there&#39;s a <a href="https://github.com/MoarVM/MoarVM/pull/1507">bug in mkdir</a> that makes it happily returns even if the target path is a file.  I&#39;m trying to fix it at the moment but <a href="https://github.com/rakudo/rakudo/pull/4408">a test</a> is still failing.  <em>Update: it passed after a maintainer bumped the dependencies to the patched version.</em></p>
<p>Anyway, back to our clipboard manager.  Here we are using <a href="https://docs.raku.org/language/control">flow controllers</a> such as <code>unless</code> and <code>when</code> in the form of <em>statement modifiers</em>, which can sometimes be easier on eyes keeping the code flat.  Existence checks like <code>e</code> &#40;exists&#41; and <code>f</code> &#40;file&#41; are also really handy.  Next, we check on the <code>order</code>:</p>
<pre><code class="language-sh">constant &#36;ALNUM &#61; &#39;ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789&#39;;sub valid&#40;&#36;path&#41; &#123;
    return False unless &#36;path.f;
    so /^\w\w&#43;&#36;/ &amp;&amp; .chars &#61;&#61; .comb.unique given trim slurp &#36;path
&#125;my &#36;order &#61; &#36;base / &#39;order&#39;;
spurt &#36;order, &#36;ALNUM unless valid &#36;order;</code></pre>
<p>Instead of printable, we only allow alphanumerics and fallback to the uppercase ones &#40;mainly because my screen can only fit as much vertically&#41;, unless <code>&#36;XDG_CACHE_HOME/threa/order</code> is a file, contains at least two unique alphanumerics &#40;exclusively&#41;.  Reading and writing files in Raku is incredibly trivial, just <code>slurp</code> and <code>spurt</code> the path.  Since we are not interested in whitespaces, they are <code>trim</code>&#39;ed from <code>order</code>.  Notice that Raku allows subroutines to be called without any parentheses—I love Lisp, but opening parenthesis <em>after</em> the function name always confuses me, especially when nested.</p>
<p>As you might have guessed, <code>given</code> is another statement modifier setting the <a href="https://docs.raku.org/syntax/&#36;_">topic variable</a> that is particularly useful in <a href="https://raku-advent.blog/2020/12/22/draft-whats-the-point-of-point-free-programming">pointfree programming</a>, where regular expressions &#40;e.g. <code>/^\w\w&#43;&#36;/</code>&#41; are matched against directly and methods are called without specifying the object.  Raku is also a weakly-typed language: <code>.comb.unique</code> &#40;a list of unique characters&#41; is coerced into an integer when compared to one &#40;number of <code>.chars</code>&#41;.</p>
<h3 id="comparing_and_saving_selections">Comparing and Saving Selections</h3>
<p>What do we do with the order then?  First we can determine the latest selection and compare it to the ones we got from <code>xclip</code> earlier to see which one is really new.  We&#39;ll also need to rotate the order, i.e. write the new selection to the <code>&#36;last</code> file and move it in front of the others that we <code>&#36;keep</code> as-is:</p>
<pre><code class="language-sh">my &#40;&#36;first, &#36;keep, &#36;last&#41; &#61; do
    given trim slurp &#36;order &#123; .comb.first, .chop, .substr: *-1 &#125;
my &#40;&#36;other, &#36;content&#41; &#61; do given try slurp &#36;base / &#36;first or &#39;&#39; &#123;
    when * ne &#36;primary &#123; &#39;clipboard&#39;, &#36;primary &#125;
    when * ne &#36;clipboard &#123; &#39;primary&#39;, &#36;clipboard &#125;
&#125;</code></pre>
<p>On the first few run, probably the cache files don&#39;t exist just yet, so we fall them back to empty ones using <code>try ... or ...</code>.  We need to know the <code>&#36;other</code> selection &#40;outdated one&#41; to later synchronize them both. In case of reselection, neither is updated and we simply skip this iteration:</p>
<pre><code class="language-sh">next unless &#36;other;</code></pre>
<p>Otherwise, let&#39;s go ahead, write down the <code>&#36;content</code>, rotate <code>&#36;order</code> and synchronize with the <code>&#36;other</code> selection:</p>
<pre><code class="language-sh">my &#36;path &#61; &#36;base / &#36;last;
spurt &#36;path, &#36;content;
spurt &#36;order, &#36;last ~ &#36;keep;
run &lt;xclip -in -selection&gt;, &#36;other, &#36;path</code></pre>
<p>That&#39;s it, now put the daemon in <code>&#36;PATH</code> and run it in <code>~/.xinitrc</code> or something IDK.  If you&#39;re worried that some selection might be too big to read that you&#39;ll the next event, asynchronize the <code>qx</code> calls by prefixing them with <code>start</code>, and <code>await</code> the results later on. It is <em>that</em> easy.</p>
<h3 id="command-line_interface">Command-Line Interface</h3>
<p>Hol up, what if I want to store the cache elsewhere or use another set of characters?  <em>&quot;Then you can go right ahead and have an intercourse with yourself, you ungrateful little piece of &#91;redacted&#93;.&quot;</em>  I would have said this were I to implement this in other languages, but luckily I got Raku, and Raku got <code>sub MAIN</code>:</p>
<pre><code class="language-sh">sub MAIN&#40;
  :&#36;children where /^\w\w&#43;&#36;/ &#61; &#36;ALNUM, #&#61; alphanumerics
  :&#36;parent &#61; &#36;XDG_CACHE_HOME           #&#61; cache path
&#41; &#123;
    my &#36;snowflakes &#61; &#36;children.comb.unique.join;
    my &#36;base &#61; &#36;parent.IO / &#39;threa&#39;;
    my &#36;order &#61; &#36;base / &#39;order&#39;;    while run &#39;clipbuzz&#39; &#123;
        ...
        spurt &#36;order, &#36;snowflakes unless valid &#36;order;
        ...
    &#125;
&#125;</code></pre>
<p>No matter how cool you think this is, it is cooler, I mean, look:</p>
<pre><code class="language-console">&#36; thread --help
Usage:
  thread &#91;--children&#91;&#61;Str where &#123; ... &#125;&#93;&#93; &#91;--parent&#61;&lt;Str&gt;&#93;
  
    --children&#91;&#61;Str where &#123; ... &#125;&#93;    alphanumerics
    --parent&#61;&lt;Str&gt;                    cache path</code></pre>
<h2 id="client_implementation">Client Implementation</h2>
<h3 id="back-end">Back-End</h3>
<p>Following the Unix™ philosophy, <code>threac</code> will do only one thing and do it well: it shall take the chosen selection and <em>schedule</em> it to move to the beginning:</p>
<pre><code class="language-sh">my &#36;XDG_CACHE_HOME &#61; &#37;*ENV&lt;XDG_CACHE_HOME&gt; // path add &#36;*HOME: &#39;.cache&#39;:;sub MAIN&#40;
   &#36;choice where /^\w?&#36;/,    #&#61; alphanumeric
  :&#36;parent &#61; &#36;XDG_CACHE_HOME #&#61; cache path
&#41; &#123;
    my &#36;base &#61; &#36;parent.IO.add: &#39;threa&#39;;
    my &#36;order &#61; add &#36;base: &#39;order&#39;;
    spurt &#36;order, S/&#36;choice&#40;.*&#41;/&#36;0&#36;choice/ with &#36;order.slurp;
    my &#36;path &#61; &#36;base.add: &#36;choice;
    run &#39;xclip&#39;, &#36;path
&#125;</code></pre>
<p>The highlight here is the non-destructive substitution <code>S///</code>, which allow regex substitution in a pointfree and pure manner. Though, instead of moving <code>&#36;choice</code> to top of the deque, we place it at the bottom and use <code>xclip</code> to trigger the daemon to do it and synchronize between selections.</p>
<h3 id="front-end">Front-End</h3>
<p>Note that <code>threac</code> does not give any output: selection history &#40;by default&#41;  are stored in a standard and convenient location to be read by any front-end of choice.  For awesome I made a menu whose each entry is wired to <code>threac</code> and <code>xdotool</code> &#40;to simulate primary paste with <code>S-Insert</code>&#41; and bind the whole thing to a keyboard shortcut.</p>
<pre><code class="language-julia">local base &#61; os.getenv&#40;&quot;HOME&quot;&#41; .. &quot;/.cache/threa/&quot;
local command &#61; &quot;threac &#37;s &amp;&amp; xdotool key shift&#43;Insert&quot;
local f &#61; io.open&#40;base .. &quot;order&quot;&#41;
local order &#61; f:read&#40;&quot;*a&quot;&#41;
f:close&#40;&#41;local items &#61; &#123;&#125;
for c in order:gmatch&#40;&quot;.&quot;&#41; do
  local f &#61; io.open&#40;base .. c&#41;
  table.insert&#40;items, &#123;f:read&#40;&quot;*a&quot;&#41;:gsub&#40;&quot;\n&quot;, &quot; &quot;&#41;, function &#40;&#41;
    awful.spawn.with_shell&#40;command:format&#40;c&#41;&#41;
  end&#125;&#41;
  f:close&#40;&#41;
end
awful.menu&#123;items &#61; items, theme &#61; &#123;width &#61; 911&#125;&#125;:show&#40;&#41;</code></pre>
<h2 id="conclusion">Conclusion</h2>
<p>Through writing the clipboard manager <a href="https://sr.ht/~cnx/threa">threa</a>, which is released under <a href="https://www.gnu.org/licenses/gpl-3.0">GNU GPLv3&#43;</a> on <a href="https://sourcehut.org">SourceHut</a>, we have discovered a few features of Raku that make it a great <em>scripting</em> language:</p>
<ul>
<li><p>Out-of-box CLI support:</p>
<ul>
<li><p>Running programs and capturing output</p>
</li>
<li><p>Environment variables</p>
</li>
<li><p>File system operations</p>
</li>
<li><p>Builtin argument parser</p>
</li>
</ul>
</li>
<li><p>Concision:</p>
<ul>
<li><p>Statement modifiers</p>
</li>
<li><p>Topic variable</p>
</li>
<li><p>First-class regex</p>
</li>
<li><p>Trivial asynchronization</p>
</li>
</ul>
</li>
</ul>
<p>As a generic programming language, Raku has other classes of characteristics that makes it useful in other larger projects such as grammars &#40;i.e. regex on steroids&#41; and OOP for human beings.  It is a truly versatile language and I really hope my words can convince someone new to try it out&#33;</p>
<table class="fndef" id="fndef:1">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">I ain&#39;t proud of this, okay?</td>
    </tr>
</table><table class="fndef" id="fndef:2">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">I&#39;m obsessed with exotic languages.</td>
    </tr>
</table><table class="fndef" id="fndef:3">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">The <em>z</em>&#39;s are for Zig, how original, I know.</td>
    </tr>
</table><table class="fndef" id="fndef:4">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content">&#91;<em>citation needed</em>&#93;</td>
    </tr>
</table><table class="fndef" id="fndef:5">
    <tr>
        <td class="fndef-backref">[5]</td>
        <td class="fndef-content">Wow much environment&#33;</td>
    </tr>
</table><table class="fndef" id="fndef:6">
    <tr>
        <td class="fndef-backref">[6]</td>
        <td class="fndef-content">By some supernatural being of course&#33;</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/threa@cnx%3E&Subject=Re: Writing a Clipboard Manager">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/threa@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/threa/comments.xml</wfw:commentRss>
</item>
<item>
  <title>To Poo or Not to Poo</title>
  <link>https://cnx.gdn/blog/nopoo/index.html</link>
  <guid>https://cnx.gdn/blog/nopoo/index.html</guid>
  <description>Me experimenting with #nopoo</description>
  <category>lyf</category><category>exp</category>
  <pubDate>Sun, 23 May 2021 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="to_poo_or_not_to_poo">To Poo or Not to Poo</h1>
<p>Late April 2021, Việt Nam witnessed the beginning of the fourth wave of SARS-CoV-2 after a few months without any community case.  Soon enough, students are told to not come to their schools&#39; campus.  This happens when I was an intern at <a href="https://usth.edu.vn">USTH</a> <a href="https://ictlab.usth.edu.vn">ICTLab</a>, so I was advised to work remotely as well.  I asked for this at the start of the internship but my supervisor was rather reluctant, since there was multiple interns working together and communication in person might be most effective.  Working from home was beneficial to me in a few important ways:</p>
<ol>
<li><p>I had a three-monitor setup at home and a more comfortable space.</p>
</li>
<li><p>I could have be more flexible working hours at home.</p>
</li>
<li><p>I did not have to bike back and forth to the lab &#40;which is 4 km away&#41; twice a day<sup id="fnref:1">[1]</sup>, which could be exhausting in the hot summer.</p>
</li>
</ol>
<p>Thanks to the last point, I also sweat a lot less and as I no longer had to maintain a public appearance, I decided to give <code>#nopoo</code> a try. I had been aware of such practice for quite a few years, but had never thought of actually implementing it until I saw <a href="https://www.youtube.com/watch?v&#61;T-_HKFjxVl0">Johnny Harris&#39; vlog</a>, which I can only describe as <em>intriguing</em>.  TL;DW the journalist maintained that generally shampoos washed away <em>his</em> scalp&#39;s natural oil, and in combination with other hair products made the scalp itchy and unhealthy. <em>His</em> solution was to drop the use of all products completely and so far it had been working <em>for him</em>.<sup id="fnref:2">[2]</sup></p>
<p>Well, my head was itchy sometimes &#40;still itchy at the time of writing&#41;, alors, <a href="https://polytechnique.edu">pour la patrie, les sciences et la gloire</a>, let&#39;s do it&#33;</p>
<h2 id="day_one">Day One</h2>
<p>I was going full no poo, no soap, no baking soda, no vinegar, <em>just water, raw water</em>.  Everything was going as expected, my hair was not as fluffy as usual after washing, but it was easier to get in shape.  I didn&#39;t really style my hair.  Not as a fashion statement, I was &#40;still am&#33;&#41; just rather lazy. Usually this wasn&#39;t an issue, unless when my hair was long, it tent to cover my forehead, ears and eyes, which was arguably an uncomfortable experience. Having the hair stay in place was indeed a blessing&#33;</p>
<h2 id="day_two">Day Two</h2>
<p>My hair started to feel thicker and running hands through it no longer felt simulating.  On the bright side it looked fabulous and did not itch.</p>
<h2 id="day_four">Day Four</h2>
<p>My hair and scalp began to feel greasy.  I guess it was because I did not wash it thoroughly that day.  With just water one would need to take more effort scrubbing the hair and especially the scalps to return them to a comfortable state.  Plus my mentality got worse so my perceived experience could be exaggeratedly negative.</p>
<h2 id="day_five">Day Five</h2>
<p>I worked out and paid more attention to the hair washing process. It felt noticeably better.</p>
<h2 id="day_six">Day Six</h2>
<p>The brief revival of my mental health did not last very long:<sup id="fnref:3">[3]</sup> later that day I was completely autopiloting and accidentally poo&#39;ed myself. It felt fluffy again but I was disappointed that things did not go as planned.</p>
<h2 id="day_seven">Day Seven</h2>
<p>I decided to cut my hair.  I had been doing so for a decade when I wrote this, but I got neither better nor faster at it, so it only happens twice or thrice a year.  Of course I had to poo myself after to get rid of all the tiny pieces.</p>
<h2 id="day_eleven">Day Eleven</h2>
<p>Fast forward a few days it started to feel greasy again, but this time the hair was shorter so it was less of an issue.  I began to apply <a href="https://en.wikipedia.org/wiki/Saline_&#40;medicine&#41;">saline</a> to the hair after washing and somehow it helped a lot in improving the situation.  Saline was also my solution for face acne in my teenage year &#40;along with finger nails and pillowcase hygiene&#41;.</p>
<h2 id="day_fifteen">Day Fifteen</h2>
<p>At this point the experience had become more stable.  My scalp still itched occasionally but seemly less often than when I was poo&#39;ing more regularly.  The hair stayed in shape with merely any effort &#40;I didn&#39;t even use a comb&#41;.</p>
<p>Overall, the difference is barely noticeable otherwise but I think I will be  continuing holding my poo for another while, probably in long term. Do not let my experience speak for you, however, try it yourself if you are interested, but keep observing the effect objectively.</p>
<table class="fndef" id="fndef:1">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">I usually had lunch at home with my parents.</td>
    </tr>
</table><table class="fndef" id="fndef:2">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">Emphases <em>his</em>.<sup id="fnref:4">[4]</sup></td>
    </tr>
</table><table class="fndef" id="fndef:3">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">I later discovered that this was due to the lack of <a href="https://www.sunlightdish.com">sunlight</a>.</td>
    </tr>
</table><table class="fndef" id="fndef:4">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content">He stressed that this might not be the case for everyone.<sup id="fnref:5">[5]</sup></td>
    </tr>
</table><table class="fndef" id="fndef:5">
    <tr>
        <td class="fndef-backref">[5]</td>
        <td class="fndef-content">OK, I get it, footnotes are distracting.</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/nopoo@cnx%3E&Subject=Re: To Poo or Not to Poo">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/nopoo@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/nopoo/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Google Summer of Code 2020</title>
  <link>https://cnx.gdn/blog/2020/gsoc/index.html</link>
  <guid>https://cnx.gdn/blog/2020/gsoc/index.html</guid>
  <description>GSoC 2020 final report</description>
  <category>fun</category><category>exp</category><category>gsoc</category><category>pkg</category><category>pip</category>
  <pubDate>Mon, 31 Aug 2020 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="google_summer_of_code_2020">Google Summer of Code 2020</h1>
<p>In the summer of 2020, I worked with the contributors of <code>pip</code>, trying to improve the networking performance of the package manager. Admittedly, at the end of <a href="https://summerofcode.withgoogle.com/archive/2020/projects/6238594655584256">the internship</a> period, <a href="https://cnx.gdn/blog/2020/gsoc/article/7/#the_benchmark">the benchmark said otherwise</a>; though I really hope the clean-up and minor fixes I happened to be doing to the codebase over the summer, in addition to the implementation of parallel utils and lazy wheel, might actually help the project.</p>
<p>Personally, I learned a lot: not just about Python packaging and networking stuff, but also on how to work with others.  I am really grateful to <a href=https://github.com/pradyunsg>@pradyunsg</a> &#40;my mentor&#41;, <a href=https://github.com/chrahunt>@chrahunt</a>, <a href=https://github.com/uranusjr>@uranusjr</a>, <a href=https://github.com/pfmoore>@pfmoore</a>, <a href=https://github.com/brainwane>@brainwane</a>, <a href=https://github.com/sbidoul>@sbidoul</a>, <a href=https://github.com/xavfernandez>@xavfernandez</a>, <a href=https://github.com/webknjaz>@webknjaz</a>, <a href=https://github.com/jaraco>@jaraco</a>, <a href=https://github.com/deveshks>@deveshks</a>, <a href=https://github.com/gutsytechster>@gutsytechster</a>, <a href=https://github.com/dholth>@dholth</a>, <a href=https://github.com/dstufft>@dstufft</a>, <a href=https://github.com/cosmicexplorer>@cosmicexplorer</a> and <a href=https://github.com/ofek>@ofek</a>.  While this feels like a long shout-out list, it really isn&#39;t.  These people are the maintainers, the contributors of <code>pip</code> and/or other Python packaging projects, and more importantly, they have been more than helpful, encouraging and patient to me throughout my every activities, showing me the way when I was lost, fixing me when I was wrong, putting up with my carelessness and showing me support across different social media.</p>
<p>To best serve the community, below I have tried my best to document what I have done, how I&#39;ve done it and why I&#39;ve done it for over the last three months.  At the time of writing, some work is still in progress, so these also serve as a reference point for myself and others to reason about decisions in relevant topics.</p>
<div class="franklin-toc"><ol><li>The Main Story<ol><li>Act One: Parallelization Utilities</li><li>Act Two: Lazy Wheels</li><li>Act Three: Late Downloading</li><li>Act Four: Batch Downloading in Parallel</li></ol></li><li>The Plot Summary</li></ol></div>
<h2 id="the_main_story">The Main Story</h2>
<p>The storyline can be divided into the following four main acts.</p>
<h3 id="act_one_parallelization_utilities">Act One: Parallelization Utilities</h3>
<p>In this first act, I ensured the portibility of parallelization measures for later use in the final act.  Multithreading and multiprocessing <code>map</code> were properly fellback on platforms without full support.</p>
<ul>
<li><p><a href=https://github.com/pypa/pip/pull/8320>GH-8320</a>: Add utilities for parallelization &#40;close <a href=https://github.com/pypa/pip/pull/8169>GH-8169</a>&#41;</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8538>GH-8538</a>: Make <code>utils.parallel</code> tests tear down properly</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8504>GH-8504</a>: Parallelize <code>pip list --outdated</code> and <code>--uptodate</code> &#40;using <a href=https://github.com/pypa/pip/pull/8320>GH-8320</a>&#41;</p>
</li>
</ul>
<h3 id="act_two_lazy_wheels">Act Two: Lazy Wheels</h3>
<p>As proposed by <a href=https://github.com/cosmicexplorer>@cosmicexplorer</a> in <a href=https://github.com/pypa/pip/pull/7819>GH-7819</a>, it is possible to only download a portion of a wheel to obtain metadata during dependency resolution. Not only that this would reduce the total amount of data to be transmitted over the network in case the resolver needs to perform heavy backtracking, but also it would create a synchronization point at the end of the resolution progress where parallel downloading can be applied to the needed wheels &#40;some wheels solely serve their metadata during dependency backtracking and are not needed by the users&#41;.</p>
<ul>
<li><p><a href=https://github.com/pypa/pip/pull/8467>GH-8467</a>: Add utitlity to lazily acquire wheel metadata over HTTP</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8584>GH-8584</a>: Revise lazy wheel and its tests</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8681>GH-8681</a>: Make range requests closer to chunk size &#40;help <a href=https://github.com/pypa/pip/pull/8670>GH-8670</a>&#41;</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8716>GH-8716</a> and <a href=https://github.com/pypa/pip/pull/8730>GH-8730</a>: Disable caching for range requests</p>
</li>
</ul>
<h3 id="act_three_late_downloading">Act Three: Late Downloading</h3>
<p>During this act, the main works were refactoring to integrate the <em>lazy wheel</em> into <code>pip</code>&#39;s codebase and clean up the way for download parallelization.</p>
<ul>
<li><p><a href=https://github.com/pypa/pip/pull/8411>GH-8411</a>: Refactor <code>operations.prepare.prepare_linked_requirement</code></p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8629>GH-8629</a>: Abstract away <code>AbstractDistribution</code> in higher-level resolver code</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8442>GH-8442</a>, <a href=https://github.com/pypa/pip/pull/8532>GH-8532</a> and <a href=https://github.com/pypa/pip/pull/8588>GH-8588</a> &#40;later reworked by <a href=https://github.com/chrahunt>@chrahunt</a> in <a href=https://github.com/pypa/pip/pull/8685>GH-8685</a>&#41;: Use lazy wheel to obtain dependency information for the new resolver</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8743>GH-8743</a>: Test hash checking for <code>fast-deps</code></p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8804>GH-8804</a>: Check download directory before making range requests</p>
</li>
</ul>
<h3 id="act_four_batch_downloading_in_parallel">Act Four: Batch Downloading in Parallel</h3>
<p>The final act is mostly about the UI of the parallel download. My work involved around how the progress should be displayed and how other relevant information should be reported to the users.</p>
<ul>
<li><p><a href=https://github.com/pypa/pip/pull/8710>GH-8710</a>: Revise method fetching metadata using lazy wheels</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8722>GH-8722</a>: Dedent late download logs &#40;fix <a href=https://github.com/pypa/pip/pull/8721>GH-8721</a>&#41;</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8737>GH-8737</a>: Add a hook for batch downloading</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8771>GH-8771</a>: Parallelize wheel download</p>
</li>
</ul>
<h2>The Side Quests</h2>
<p>In order to keep the wheel turning &#40;no pun intended&#41; and avoid wasting time waiting for the pull requests above to be reviewed, I decided to create even more PRs &#40;as I am typing this, many of the patches listed below are nowhere near being merged&#41;.</p>
<ul>
<li><p><a href=https://github.com/pypa/pip/pull/7878>GH-7878</a>: Fail early when install path is not writable</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/7928>GH-7928</a>: Fix rst syntax in Getting Started guide</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/7988>GH-7988</a>: Fix tabulate col size in case of empty cell</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8137>GH-8137</a>: Add subcommand alias mechanism</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8143>GH-8143</a>: Make mypy happy with beta release automation</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8248>GH-8248</a>: Fix typo and simplify ireq call</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8332>GH-8332</a>: Add license requirement to <code>_vendor/README.rst</code></p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8423>GH-8423</a>: Nitpick logging calls</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8435>GH-8435</a>: Use str.format style in logging calls</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8456>GH-8456</a>: Lint <code>src/pip/_vendor/README.rst</code></p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8568>GH-8568</a>: Declare constants in configuration.py as such</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8571>GH-8571</a>: Clean up <code>Configuration.unset_value</code> and nit <code>__init__</code></p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8578>GH-8578</a>: Allow verbose/quiet level to be specified via config files and environment variables</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8599>GH-8599</a>: Replace tabs by spaces for consistency</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8614>GH-8614</a>: Use <code>monkeypatch.setenv</code> to mock environment variables</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8674>GH-8674</a>: Fix <code>tests/functional/test_install_check.py</code>, when run with new resolver</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8692>GH-8692</a>: Make assertion failure give better message</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8709>GH-8709</a>: List downloaded distributions before exiting &#40;fix <a href=https://github.com/pypa/pip/pull/8696>GH-8696</a>&#41;</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8759>GH-8759</a>: Allow py2 deprecation warning from setuptools</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8766>GH-8766</a>: Use the new resolver for test requirements</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8790>GH-8790</a>: Mark tests using remote svn and hg as xfail</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8795>GH-8795</a>: Reformat a few spots in user guide</p>
</li>
</ul>
<h2 id="the_plot_summary">The Plot Summary</h2>
<p>Every Monday throughout the Summer of Code, I summarized what I had done in the week before in the form of either a short blog or an &#40;even shorter&#41; check-in.  These write-ups often contain handfuls of popular culture references and was originally hosted on <a href="https://blogs.python-gsoc.org/en/mcsinyxs-blog">Python GSoC</a>.</p>
<ul>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/checkin/1>First Check-In</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/article/1>Unexpected Things When You&#39;re Expecting</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/checkin/2>Second Check-In</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/article/2>The Wonderful Wizard of O&#39;zip</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/checkin/3>Third Check-In</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/article/3>I&#39;m Not Drowning On My Own</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/checkin/4>Fourth Check-In</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/article/4>I&#39;ve Walked 500 Miles…</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/checkin/5>Fifth Check-In</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/article/5>Sorting Things Out</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/checkin/6>Sixth Check-In</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/article/6>Parallelizing Wheel Downloads</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/checkin/7>Final Check-In</a></p>
</li>
<li><p><a href=https://cnx.gdn/blog/2020/gsoc/article/7>Outro</a></p>
</li>
</ul>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/2020/gsoc@cnx%3E&Subject=Re: Google Summer of Code 2020">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/2020/gsoc@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/2020/gsoc/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Outro</title>
  <link>https://cnx.gdn/blog/2020/gsoc/article/7/index.html</link>
  <guid>https://cnx.gdn/blog/2020/gsoc/article/7/index.html</guid>
  <description>GSoC 2020: Outro</description>
  <category>gsoc</category><category>pip</category><category>python</category>
  <pubDate>Mon, 31 Aug 2020 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="outro">Outro</h1>
<blockquote>
<p>Steamed fish was amazing, matter of fact<br />Let me get some jerk chicken to go<br />Grabbed me one of them lemon pie theories<br />And let me get some of them benchmarks you theories too</p>
</blockquote>
<div class="franklin-toc"><ol><li>The Look</li><li>The Benchmark<ol><li>Average Distribution</li><li>Large Distribution</li><li>Distribution with Conflicting Dependencies</li></ol></li><li>What Now?</li></ol></div>
<h2 id="the_look">The Look</h2>
<p>At the time of writing, <a href=https://github.com/pypa/pip/pull/8771>implementation-wise parallel download is ready</a>:</p>
<p><a href="https://asciinema.org/a/356704"><img src="https://cnx.gdn/assets/pip-8771.svg" alt="asciicast" /></a></p>
<p>Does this mean I&#39;ve finished everything just-in-time?  This sounds to good to be true&#33;  And how does it perform?  Welp...</p>
<h2 id="the_benchmark">The Benchmark</h2>
<p>Here comes the bad news: under a decent connection to the package index, using <code>fast-deps</code> does not make <code>pip</code> faster.  For best comparison, I will time <code>pip download</code> on the following cases:</p>
<h3 id="average_distribution">Average Distribution</h3>
<p>For convenience purposes, let&#39;s refer to the commands to be used as follows</p>
<pre><code class="language-console">&#36; pip --no-cache-dir download &#123;requirement&#125;  # legacy-resolver
&#36; pip --use-feature&#61;2020-resolver \
   --no-cache-dir download &#123;requirement&#125;  # 2020-resolver
&#36; pip --use-feature&#61;2020-resolver --use-feature&#61;fast-deps \
   --no-cache-dir download &#123;requirement&#125;  # fast-deps</code></pre>
<p>In the first test, I used <a href="https://sr.ht/~cnx/axuy">axuy</a> and obtained the following results</p><table><tr><th align="right">legacy-resolver</th><th align="right">2020-resolver</th><th align="right">fast-deps</th></tr><tr><td align="right">7.709s</td><td align="right">7.888s</td><td align="right">10.993s</td></tr><tr><td align="right">7.068s</td><td align="right">7.127s</td><td align="right">11.103s</td></tr><tr><td align="right">8.556s</td><td align="right">6.972s</td><td align="right">10.496s</td></tr></table><p>Funny enough, running <code>pip download</code> with <code>fast-deps</code> in a directory with downloaded files already took around 7-8 seconds.  This is because to lazily download a wheel, <code>pip</code> has to <a href=https://github.com/pypa/pip/pull/8670>make many requests</a> which are apparently more expensive than actual data transmission on my network.</p>
<div class="admonition note"><p class="admonition-title">When is it useful then?</p><p>With unstable connection to PyPI &#40;for some reason I am not confident enough to state&#41;, this is what I got</p><table><tr><th align="right">2020-resolver</th><th align="right">fast-deps</th></tr><tr><td align="right">1m16.134s</td><td align="right">0m54.894s</td></tr><tr><td align="right">1m0.384s</td><td align="right">0m40.753s</td></tr><tr><td align="right">0m50.102s</td><td align="right">0m41.988s</td></tr></table><p>As the connection was <em>unstable</em> and that the majority of <code>pip</code> networking is performed as CI/CD with large and stable bandwidth, I am unsure what this result is supposed to tell &#40;-;</p>
</div>
<h3 id="large_distribution">Large Distribution</h3>
<p>In this test, I used <a href="https://www.tensorflow.org">TensorFlow</a> as the requirement and obtained the following figures:</p><table><tr><th align="right">legacy-resolver</th><th align="right">2020-resolver</th><th align="right">fast-deps</th></tr><tr><td align="right">0m52.135s</td><td align="right">0m58.809s</td><td align="right">1m5.649s</td></tr><tr><td align="right">0m50.641s</td><td align="right">1m14.896s</td><td align="right">1m28.168s</td></tr><tr><td align="right">0m49.691s</td><td align="right">1m5.633s</td><td align="right">1m22.131s</td></tr></table><h3 id="distribution_with_conflicting_dependencies">Distribution with Conflicting Dependencies</h3>
<p>Some requirement that will trigger a decent amount of backtracking by the current implementation of the new resolver <code>oslo-utils&#61;&#61;1.4.0</code>:</p><table><tr><th align="right">2020-resolver</th><th align="right">fast-deps</th></tr><tr><td align="right">14.497s</td><td align="right">24.010s</td></tr><tr><td align="right">17.680s</td><td align="right">28.884s</td></tr><tr><td align="right">16.541s</td><td align="right">26.333s</td></tr></table><h2 id="what_now">What Now?</h2>
<p>I don&#39;t know, to be honest.  At this point I&#39;m feeling I&#39;ve failed my own &#40;and that of other stakeholders of <code>pip</code>&#41; expectation and wasted the time and effort of <code>pip</code>&#39;s maintainers reviewing dozens of PRs I&#39;ve made in the last three months.</p>
<p>On the bright side, this has been an opportunity for me to explore the codebase of package manager and discovered various edge cases where the new resolver has yet to cover &#40;e.g. I&#39;ve just noticed that <code>pip download</code> would save to-be-discarded distributions, I&#39;ll file an issue on that soon&#41;.  Plus I got to know many new and cool people and idea, which make me a more helpful individual to work on Python packaging in the future, I hope.</p>
<p></p>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/2020/gsoc/article/7@cnx%3E&Subject=Re: Outro">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/2020/gsoc/article/7@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/2020/gsoc/article/7/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Parallelizing Wheel Downloads</title>
  <link>https://cnx.gdn/blog/2020/gsoc/article/6/index.html</link>
  <guid>https://cnx.gdn/blog/2020/gsoc/article/6/index.html</guid>
  <description>GSoC 2020: Parallelizing Wheel Downloads</description>
  <category>gsoc</category><category>pip</category><category>python</category>
  <pubDate>Mon, 17 Aug 2020 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="parallelizing_wheel_downloads">Parallelizing Wheel Downloads</h1>
<blockquote>
<p>And now it&#39;s clear as this promise<br />That we&#39;re making<br />Two progress bars into one</p>
</blockquote><p>Hello there&#33; It has been raining a lot lately and some mosquito has given me the Dengue fever today.  To whoever reading this, I hope it would never happen to you.</p>
<h2>Download Parallelization</h2>
<p>I&#39;ve been working on <code>pip</code>&#39;s download parallelization for quite a while now. As distribution download in <code>pip</code> was modeled as a lazily evaluated iterable of chunks, parallelizing such procedure is as simple as submitting routines that write files to disk to a worker pool.</p>
<p>Or at least that is what I thought.</p>
<h2>Progress Reporting UI</h2>
<p><code>pip</code> is currently using customly defined progress reporting classes, which was not designed to working with multithreading code.  Firstly, I want to try using these instead of defining separate UI for multithreaded progresses. As they use system signals for termination, one must the progress bars has to be running the main thread.  Or sort of.</p>
<p>Since the progress bars are designed as iterators, I realized that we can call <code>next</code> on them.  So quickly, I throw in some queues and locks, and prototyped the first <em>working</em> <a href=https://github.com/pypa/pip/pull/8771>implementation of
progress synchronization</a>.</p>
<h2>Performance Issues</h2>
<p>Welp, I only said that it works, but I didn&#39;t mention the performance, which is terrible.  I am pretty sure that the slow down is with the synchronization, since the <code>map_multithread</code> call doesn&#39;t seem to trigger anything that may introduce any sort of blocking.</p>
<p>This seems like a lot of fun, and I hope I&#39;ll get better tomorrow to continue playing with it&#33;</p>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/2020/gsoc/article/6@cnx%3E&Subject=Re: Parallelizing Wheel Downloads">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/2020/gsoc/article/6@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/2020/gsoc/article/6/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Sorting Things Out</title>
  <link>https://cnx.gdn/blog/2020/gsoc/article/5/index.html</link>
  <guid>https://cnx.gdn/blog/2020/gsoc/article/5/index.html</guid>
  <description>GSoC 2020: Sorting Things Out</description>
  <category>gsoc</category><category>pip</category><category>python</category>
  <pubDate>Mon, 03 Aug 2020 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="sorting_things_out">Sorting Things Out</h1>
<p>Hi&#33;  I really hope that everyone reading this is still doing okay, and if that isn&#39;t the case, I wish you a good day&#33;</p>
<h2 id="pip_202_released"><code>pip</code> 20.2 Released&#33;</h2>
<p>Last Wednesday, <code>pip</code> 20.2 was released, delivering the <code>2020-resolver</code> as well as many other improvements&#33;  I was lucky to be able to get the <code>fast-deps</code> feature to be included as part of the release. A brief description of this <em>experimental</em> feature as well as testing instruction can be found on <a href="https://discuss.python.org/t/announcement-pip-20-2-release/4863/2">Python Discuss</a>.</p>
<p>The public exposure of the feature also remind me of some further <a href=https://github.com/pypa/pip/pull/8681>optimization</a> to make on <a href=https://github.com/pypa/pip/pull/8670>the lazy wheel</a>. Hopefully without download parallelization it would not be too slow to put off testing by concerned users of <code>pip</code>.</p>
<h2 id="preparation_for_download_parallelization">Preparation for Download Parallelization</h2>
<p>As of this moment, we already have:</p>
<ul>
<li><p><a href=https://github.com/pypa/pip/pull/8162#issuecomment-667504162>Multithreading pool fallback working</a></p>
</li>
<li><p>An opt-in to use lazy wheel to optain dependency information, and thus getting a list of wheels at the end of resolution ready to be downloaded together</p>
</li>
</ul>
<p>What&#39;s left is <em>only</em> to interject a parallel download somewhere after the dependency resolution step.  Still, this struggles me way more than I&#39;ve ever imagined.  I got so stuck that I had to give myself a day off in the middle of the week &#40;and study some Rust&#41;, then I came up with <a href=https://github.com/pypa/pip/pull/8638>something what was agreed upon as difficult to maintain</a>.</p>
<p>Indeed, a large part of this is my fault, for not communicating the design thoroughly with <code>pip</code>&#39;s maintainers and not carefully noting stuff down during &#40;verbal&#41; discussions with my mentor.  Thankfully <a href=https://github.com/pypa/pip/pull/8685>Chris Hunt came to the rescue</a> and did a refactoring that will make my future work much easier and cleaner.</p>
    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/2020/gsoc/article/5@cnx%3E&Subject=Re: Sorting Things Out">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/2020/gsoc/article/5@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/2020/gsoc/article/5/comments.xml</wfw:commentRss>
</item>
<item>
  <title>I&#39;ve Walked 500 Miles…</title>
  <link>https://cnx.gdn/blog/2020/gsoc/article/4/index.html</link>
  <guid>https://cnx.gdn/blog/2020/gsoc/article/4/index.html</guid>
  <description>GSoC 2020: I&#39;ve Walked 500 Miles…</description>
  <category>gsoc</category><category>pip</category><category>python</category>
  <pubDate>Mon, 20 Jul 2020 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="ive_walked_500_miles">I&#39;ve Walked 500 Miles…</h1>
<blockquote>
<p>... and I would walk 500 more<br />Just to be the man who walks a thousand miles<br />To fall down at your door</p>
<p><img src="https://cnx.gdn/assets/500-miles.gif" alt="500 miles" /></p>
</blockquote>
<div class="franklin-toc"><ol><li>The Main Road</li><li>The Side Quests</li><li>Snap Back to Reality</li></ol></div>
<h2 id="the_main_road">The Main Road</h2>
<p>Hi, have you met <code>fast-deps</code>?  It&#39;s &#40;going to be&#41; the name of <code>pip</code>&#39;s experimental feature that may improve the speed of dependency resolution of the new resolver.  By avoid downloading whole wheels to just obtain metadata, it is especially helpful when <code>pip</code> has to do heavy backtracking to resolve conflicts.</p>
<p>Thanks to <a href=https://github.com/pypa/pip/pull/8532#discussion_r453990728>Chris Hunt's review on GH-8537</a>, my mentor Pradyun Gedam and I worked out a less hacky approach to inteject the call to lazy wheel during the resolution process.  A new PR <a href=https://github.com/pypa/pip/pull/8588>GH-8588</a> was filed to implement it—I could have <em>just</em> worked on top of the old PR and rebased, but my <code>git</code> skill is far from gud enuff to confidently do it.</p>
<p>Testing this one has been a lot of fun though.  At first, integration tests were added as a rerun of the tests for the new resolver, with an additional flag to use feature <code>fast-deps</code>.  It indeed made me feel guilty towards <a href="https://travis-ci.com">Travis</a>, who has to work around 30 minutes more every run. Per Chris Hunt&#39;s suggestion, in the new PR, I instead write a few functional tests for the area relating the most to the feature, namely <code>pip</code>&#39;s subcommands <code>wheel</code>, <code>download</code> and <code>install</code>.</p>
<p>It was also suggested that a mock server with HTTP range requests support might be better &#40;in term of performance and reliablilty&#41; than for testing. However, <a href=https://github.com/pypa/pip/pull/8584#issuecomment-659227702>I have yet to be able to make
Werkzeug do it</a>.</p>
<p>Why did I say I&#39;m half way there?  With the parallel utilities merged and a way to quickly get the list of distribution to be downloaded being really close, what left is <em>only</em> to figure out a way to properly download them in parallel. With no distribution to be added during the download progress, the model of this will fit very well with the architecture in <a href="https://cnx.gdn/assets/pip-parallel-dl.pdf">my original proposal</a>. A batch downloader can be implemented to track the progress of each download and thus report them cleanly as e.g. progress bar or percentage. This is the part I am second-most excited about of my GSoC project this summer &#40;after the synchronization of downloads written in my proposal, which was then superseded by <code>fast-deps</code>&#41; and I can&#39;t wait to do it&#33;</p>
<h2 id="the_side_quests">The Side Quests</h2>
<p>As usual, I make sure that I complete every side quest I see during the journey:</p>
<ul>
<li><p><a href=https://github.com/pypa/pip/pull/8568>GH-8568</a>: Declare constants in <code>configuration.py</code> as such</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8571>GH-8571</a>: Clean up <code>Configuration.unset_value</code> and nit the class&#39; <code>__init__</code></p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8578>GH-8578</a>: Allow verbose/quite level to be specified via config file and env var</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8599>GH-8599</a>: Replace tabs by spaces for consistency</p>
</li>
</ul>
<h2 id="snap_back_to_reality">Snap Back to Reality</h2>
<p>A bit about me, I actually walked 500 meters earlier today to a bank and walked 500 more to another to prepare my Visa card for purchasing the upcoming <a href="https://wiki.pine64.org/index.php/PinePhone">PinePhone</a> prototype.  It&#39;s one of the first smartphones to fully support a GNU/Linux distribution, where one can run desktop apps &#40;including proper terminals&#41; as well as traditional services like SSH, HTTP server and IPFS node because why not?  Just a few hours ago, I pre-ordered the <a href="https://postmarketos.org/blog/2020/07/15/pinephone-ce-preorder/">postmarketOS community edition</a> with additional hardware for convergence.</p>
<p>If you did not come here for a PinePhone ad, please take my apologies though d-; and to ones reading this, I hope you all can become the person who walks a thousand miles to fall down at the door opening to all what you ever wished for&#33;</p>
<p></p>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/2020/gsoc/article/4@cnx%3E&Subject=Re: I&#39;ve Walked 500 Miles…">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/2020/gsoc/article/4@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/2020/gsoc/article/4/comments.xml</wfw:commentRss>
</item>
<item>
  <title>I&#39;m Not Drowning On My Own</title>
  <link>https://cnx.gdn/blog/2020/gsoc/article/3/index.html</link>
  <guid>https://cnx.gdn/blog/2020/gsoc/article/3/index.html</guid>
  <description>GSoC 2020: I&#39;m Not Drowning On My Own</description>
  <category>gsoc</category><category>pip</category><category>python</category>
  <pubDate>Mon, 06 Jul 2020 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="im_not_drowning_on_my_own">I&#39;m Not Drowning On My Own</h1>
<div class="franklin-toc"><ol><li>Cold Water</li><li>Warm Water</li><li>Learning How To Swim</li><li>Diving Plan</li></ol></div>
<h2 id="cold_water">Cold Water</h2>
<p>Hello there&#33;  My schoolyear is coming to an end, with some final assignments and group projects left to be done.  I for sure underestimated the workload of these and in the last &#40;and probably next&#41; few days I&#39;m drowning in work trying to meet my deadlines.</p>
<p>One project that might be remotely relevant is <a href="https://github.com/McSinyx/cheese-shop">cheese-shop</a>, which tries to manage the metadata of packages from the real <a href="https://pypi.org">Cheese Shop</a>.  Other than that, schoolwork is draining a lot of my time and I can&#39;t remember the last time I came up with something new for my GSoC project &#41;-;</p>
<h2 id="warm_water">Warm Water</h2>
<p>On the bright side, I received a lot of help and encouragement from contributors and stakeholders of <code>pip</code>.  In the last week alone, I had five pull requests merged:</p>
<ul>
<li><p><a href=https://github.com/pypa/pip/pull/8332>GH-8332</a>: Add license requirement to <code>_vendor/README.rst</code></p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8320>GH-8320</a>: Add utilities for parallelization</p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8504>GH-8504</a>: Parallelize <code>pip list --outdated</code> and <code>--uptodate</code></p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8411>GH-8411</a>: Refactor <code>operations.prepare.prepare_linked_requirement</code></p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8467>GH-8467</a>: Add utitlity to lazily acquire wheel metadata over HTTP</p>
</li>
</ul>
<p>In addition to helping me getting my PRs merged, my mentor Pradyun Gedam also gave me my first official feedback, including what I&#39;m doing right &#40;and wrong too&#33;&#41; and what I should keep doing to increase the chance of the project being successful.</p>
<p><a href=https://github.com/pypa/pip/pull/7819>GH-7819</a>&#39;s roadmap &#40;Danny McClanahan&#39;s discoveries and works on lazy wheels&#41; is being closely tracked by <code>hatch</code>&#39;s maintainter Ofek Lev, which really makes me proud and warms my heart, that what I&#39;m helping build is actually needed by the community&#33;</p>
<h2 id="learning_how_to_swim">Learning How To Swim</h2>
<p>With <a href=https://github.com/pypa/pip/pull/8467>GH-8467</a> and <a href=https://github.com/pypa/pip/pull/8530>GH-8530</a> merged, I&#39;m now working on <a href=https://github.com/pypa/pip/pull/8532>GH-8532</a> which aims to roll out the lazy wheel as the way to obtain dependency information via the CLI flag <code>--use-feature&#61;lazy-wheel</code>.</p>
<p><a href=https://github.com/pypa/pip/pull/8532>GH-8532</a> was failing initially, despite being relatively trivial and that the commit it used to base on was passing.  Surprisingly, after rebasing it on top of <a href=https://github.com/pypa/pip/pull/8530>GH-8530</a>, it suddenly became green mysteriously.  After the first &#40;early&#41; review, I was able to iterate on my earlier code, which used the ambiguous exception <code>RuntimeError</code>.</p>
<p>The rest to be done is <em>just</em> adding some functional tests &#40;I&#39;m pretty sure this will be either overwhelming or underwhelming&#41; to make sure that the command-line flag is working correctly.  Hopefully this can make it into the beta of the upcoming release <a href=https://github.com/pypa/pip/pull/8511>this month</a>.</p>
<p><img src="https://cnx.gdn/assets/lazy-wheel.jpg" alt="Lazy wheel" /></p>
<p>In other news, I&#39;ve also submitted <a href=https://github.com/pypa/pip/pull/8538>a patch improving the tests
for the parallelization utilities</a>, which was really messy as I wrote them. Better late than never&#33;</p>
<p>Metaphors aside, I actually can&#39;t swim d-:</p>
<h2 id="diving_plan">Diving Plan</h2>
<p>After <a href=https://github.com/pypa/pip/pull/8532>GH-8532</a>, I think I&#39;ll try to parallelize downloads of wheels that are lazily fetched only for metadata.  By the current implementation of the new resolver, for <code>pip install</code>, this can be injected directly between the resolution and build/installation process.</p>
<p></p>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/2020/gsoc/article/3@cnx%3E&Subject=Re: I&#39;m Not Drowning On My Own">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/2020/gsoc/article/3@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/2020/gsoc/article/3/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Teredo Tunnel Simulation</title>
  <link>https://cnx.gdn/blog/teredo/index.html</link>
  <guid>https://cnx.gdn/blog/teredo/index.html</guid>
  <description>Teredo tunnel simulation in virtual machines</description>
  <category>fun</category><category>recipe</category><category>net</category>
  <pubDate>Fri, 03 Jul 2020 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="teredo_tunnel_simulation">Teredo Tunnel Simulation</h1>
<p>Internet Protocol version 6 &#40;IPv6&#41;, the most recent version of the Internet Protocol, was developed by the IETF to deal with the long-anticipated problem of IPv4 address exhaustion.  Despite being superior to IPv4 in multiple aspect &#40;e.g. larger address space, extension headers&#41;, IPv6 has not been widely adopted, although it has been semi-standardized in 1998 and fully-standardized in 2017.<sup id="fnref:rfc8200">[1]</sup></p>
<p>During the transition period, teredo tunneling has been used to give IPv6 connectivity for IPv6-capable hosts that are on the IPv4 Internet but have no native connection to an IPv6 network.<sup id="fnref:rfc4380">[2]</sup> In this article, I will demontrate a way to set up such tunnel up on virtual machines, then examine the packets being sent by IPv6 nodes connected by the tunnel.</p>
<div class="franklin-toc"><ol><li>Configuration<ol><li>Virtual Machines</li><li>Teredo Tunnel Setup</li><li>Teredo Tunnel Usage</li></ol></li><li>Analysis<ol><li>Packets Capturing</li><li>Packet Contents<ol><li>Ethernet Header</li><li>IPv4 Header</li><li>IPv6 Header</li><li>ICMPv6</li></ol></li></ol></li><li>Conclusion</li></ol></div>
<h2 id="configuration">Configuration</h2><figure>
  <a href=https://video.hardlimit.com/w/d4dYuXm6p4g9sCWm156qkg>
    <img src=https://cnx.gdn/assets/teredo.png alt=Screenshot>
  </a>
  <figcaption>Teredo Tunnel Simulation Speedrun</figcaption>
</figure><h3 id="virtual_machines">Virtual Machines</h3>
<p>In order to simulate Teredo tunneling, one needs two IPv6 nodes and two routers with both IPv4 and IPv6 access.  In total, there needs to be four virtual machines to be set up, thus I went for Void Linux, which is known for its low memory foot print thanks to using <code>runit</code> instead of <code>systemd</code>. To minimize resource usage and speed up the setup process, I chose the barebone live image which uses <code>musl</code> instead of <code>glibc</code>.  At boot, the image uses only 40 MB of memory.</p>
<p>For virtualization, I used <code>vert-manager</code>, simply because it is available in Debian&#39;s repository &#40;my host OS&#41;.  For some reason, on <code>amd64</code>, the kernel refuses to boot until I give it over 200 MB, but apparently that is still a really modest number.  Networking is provided to the guest OSes via NAT with default configurations.</p>
<p>It is worth mentioning that through <code>virtio</code>, one may use SSH to log into the guests systems from the host OS.  I find this especially convenient as it enables me to copy and paste not only commands but also IP addresses between host and guests as well as between guests.</p>
<p>For convenience, from now on, the outside nodes will be referred to as PC A and PC B, on the other hand the routers are named Router A and Router B.  Upon boot, they were given an Ethernet interface <code>eth0</code> with the following addresses.</p><table><tr><th align="left">Node</th><th align="left">MAC address</th><th align="left">IPv4 address</th></tr><tr><td align="left">Router A</td><td align="left"><code>52:54:00:f0:85:c7</code></td><td align="left"><code>192.168.122.127</code></td></tr><tr><td align="left">Router B</td><td align="left"><code>52:54:00:2b:01:cc</code></td><td align="left"><code>192.168.122.134</code></td></tr><tr><td align="left">PC A</td><td align="left"><code>52:54:00:3b:82:36</code></td><td align="left"><code>192.168.122.86</code></td></tr><tr><td align="left">PC B</td><td align="left"><code>52:54:00:7b:ed:c0</code></td><td align="left"><code>192.168.122.255</code></td></tr></table><p>Local IPv6 addresses were also given but we are not going to need them.</p>
<h3 id="teredo_tunnel_setup">Teredo Tunnel Setup</h3>
<p>First, I set up a IPv4 tunnel between the two routers:</p>
<pre><code class="language-sh"># On Router A
ip tunnel add tunn mode sit remote 192.168.122.134 ttl 255
ip link set tunn up
# On Router B
ip tunnel add tunn mode sit remote 192.168.122.127 ttl 255
ip link set tunn up</code></pre>
<p>For this tunnel to be able to act as a Teredo one, the two routers needs to have IPv6 addresses prefixed by <code>2001::/32</code>.<sup id="fnref:rfc4380">[2]</sup></p>
<pre><code class="language-sh"># On Router A
ip -6 addr add 2001:2::1/64 dev eth0
# On Router B
ip -6 addr add 2001:3::1/64 dev eth0</code></pre>
<p>Finally, I fellback all IPv6 lookups to the tunnel and enabled IPv6 forwarding:</p>
<pre><code class="language-sh">ip -6 route add default dev tunn
sysctl -w net.ipv6.conf.all.forwarding&#61;1</code></pre>
<h3 id="teredo_tunnel_usage">Teredo Tunnel Usage</h3>
<p>The IPv6 addresses of the PCs were set up as follows &#40;<code>0x8067</code> is <code>PC</code> in ASCII&#41;.</p>
<pre><code class="language-sh"># On PC A
ip -6 address add 2001:2::8067/64 dev eth0
# On PC B
ip -6 address add 2001:3::8067/64 dev eth0</code></pre>
<p>By giving both Router A and PC A addresses prefixed by <code>2001:2::/64</code> &#40;similarly for Router B and PC B&#41;, I implied that they can find each other through the local IPv6 network, for example on PC B:</p>
<pre><code class="language-console">&#36; ip -6 route | head -n1
2001:3::/64 dev eth0 proto kernel metric 256 pref medium</code></pre>
<p>To use the newly created tunnel, the PCs simple had to be routed directly to the routers:</p>
<pre><code class="language-sh"># On PC A
ip -6 route add default via 2001:2::1
# On PC B
ip -6 route add default via 2001:3::1</code></pre>
<p>The connection could then be verified by running on PC A:</p>
<pre><code class="language-console">&#36; traceroute 2001:3::8067
traceroute to 2001:3::8067 &#40;2001:3::8067&#41;, 30 hops max, 80 byte packets
 1  2001:2::1 &#40;2001:2::1&#41;  0.572 ms  0.441 ms  0.328 ms
 2  2001:3::1 &#40;2001:3::1&#41;  0.906 ms  0.888 ms  1.049 ms
 3  2001:3::8067 &#40;2001:3::8067&#41;  1.325 ms  1.174 ms  1.091 ms</code></pre>
<h2 id="analysis">Analysis</h2>
<p>To gain further understanding on how packets are transferred over the Teredo tunnel, I captured and took a closer look at some of them.</p>
<h3 id="packets_capturing">Packets Capturing</h3>
<p>Fortunately for me<sup id="fnref:ipfs">[3]</sup>, all traffic of guests OSes were wired to an separate interface named <code>virbr0</code>.  To capture going through the tunnel, I simply had to tell Wireshark to listen to the interface, while letting PC A ping PC B though IPv6: <code>ping -c1 2001:3::8067</code>. I then skimmed through the packets sent between the two nodes and looked for the IPv6-in-IPv4 ones.</p>
<h3 id="packet_contents">Packet Contents</h3>
<p>Catured IPv6-in-IPv4 looks exactly like how I would imagined it to be. The content of the ping request can be partially decoded as follows.</p>
<h4 id="ethernet_header">Ethernet Header</h4>
<ul>
<li><p><code>52 54 00 2b 01 cc</code>: MAC address of Router B &#40;destination&#41;</p>
</li>
<li><p><code>52 54 00 f0 85 c7</code>: MAC address of Router A &#40;source&#41;</p>
</li>
<li><p><code>08 00</code>: EtherType of IPv4</p>
</li>
</ul>
<h4 id="ipv4_header">IPv4 Header</h4>
<ul>
<li><p><code>45 00 00 7c 9b 43 40 00 ff</code>: Some flags</p>
</li>
<li><p><code>29</code>: Protocol of <em>IPv6</em></p>
</li>
<li><p><code>69 be</code>: Checksum</p>
</li>
<li><p><code>c0 a8 7a 86</code>: IPv4 address of Router B &#40;destination&#41;</p>
</li>
<li><p><code>c0 a8 7a 7f</code>: IPv4 address of Router A &#40;source&#41;</p>
</li>
</ul>
<h4 id="ipv6_header">IPv6 Header</h4>
<ul>
<li><p><code>60 00 07 e7 00 40</code>: Some flags</p>
</li>
<li><p><code>3a</code>: Next header &#40;ICMPv6&#41;</p>
</li>
<li><p><code>3f</code>: Hop limit of 63</p>
</li>
<li><p><code>20 01 00 02 00 00 00 00 00 00 00 00 00 00 80 67</code>: PC A&#39;s IPv6 address</p>
</li>
<li><p><code>20 01 00 03 00 00 00 00 00 00 00 00 00 00 80 67</code>: PC B&#39;s IPv6 address</p>
</li>
</ul>
<h4 id="icmpv6">ICMPv6</h4>
<ul>
<li><p><code>80</code>: Type of ping request</p>
</li>
<li><p><code>00 cf be 03 d9 00 01</code>: Some flags</p>
</li>
<li><p><code>e3 0d fe 5e 00 00 00 00 bc d6 0e 00 00 00
  00 00 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d
  1e 1f 20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d
  2e 2f 30 31 32 33 34 35 36 37</code>: Binary data to be echoed</p>
</li>
</ul>
<h2 id="conclusion">Conclusion</h2>
<p>Via the activities elaborated above, the procedure to set up a Teredo tunnel and the content of the packets travelling through it could be well understood. These understanding may help facilite the adoption of IPv6, even for IPv6 nodes having no native connection to an IPv6 network.  I hope that the IPv6 will grow fast enough that I can see the day measures like this tunnel can soon be deprecated.</p>
<table class="fndef" id="fndef:rfc8200">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content"><a href="https://tools.ietf.org/html/rfc8200">RFC 8200</a></td>
    </tr>
</table><table class="fndef" id="fndef:rfc4380">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content"><a href="https://tools.ietf.org/html/rfc4380">RFC 4380</a></td>
    </tr>
</table><table class="fndef" id="fndef:ipfs">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">Aside from web browsing, I also run an IPFS node and a bunch of local servers.  I probably need to retire some of them soon since they really clutter the traffic.</td>
    </tr>
</table>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/teredo@cnx%3E&Subject=Re: Teredo Tunnel Simulation">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/teredo@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/teredo/comments.xml</wfw:commentRss>
</item>
<item>
  <title>The Wonderful Wizard of O&#39;zip</title>
  <link>https://cnx.gdn/blog/2020/gsoc/article/2/index.html</link>
  <guid>https://cnx.gdn/blog/2020/gsoc/article/2/index.html</guid>
  <description>GSoC 2020: The Wonderful Wizard of O&#39;zip</description>
  <category>gsoc</category><category>pip</category><category>python</category><category>net</category>
  <pubDate>Mon, 22 Jun 2020 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="the_wonderful_wizard_of_ozip">The Wonderful Wizard of O&#39;zip</h1>
<blockquote>
<p>Never give up... No one knows what&#39;s going to happen next.</p>
</blockquote>
<div class="franklin-toc"><ol><li>Preface</li><li>The <code>multiprocessing&#91;.dummy&#93;</code> wrapper</li><li>The file-like object mapping ZIP over HTTP</li><li>What&#39;s next?</li></ol></div>
<h2 id="preface">Preface</h2>
<p>Greetings and best wishes&#33;  I had a lot of fun during the last week, although admittedly nothing was really finished.  In summary, these are the works I carried out in the last seven days:</p>
<ul>
<li><p>Finilizing <a href=https://github.com/pypa/pip/pull/8320>utilities for parallelization</a></p>
</li>
<li><p><a href=https://github.com/pypa/pip/pull/8467>Continuing experimenting</a> on <a href=https://github.com/pypa/pip/pull/8442>using lazy wheels or dependency resolution</a></p>
</li>
<li><p>Polishing up <a href=https://github.com/pypa/pip/pull/8411>the patch</a> refactoring <code>operations.prepare.prepare_linked_requirement</code></p>
</li>
<li><p>Adding <code>flake8-logging-format</code> <a href=https://github.com/pypa/pip/pull/8423#issuecomment-645418725>to the linter</a></p>
</li>
<li><p>Splitting <a href=https://github.com/pypa/pip/pull/8456>the linting patch</a> from <a href=https://github.com/pypa/pip/pull/8332>the PR adding
  the license requirement to vendor README</a></p>
</li>
</ul>
<h2 id="the_multiprocessingdummy_wrapper">The <code>multiprocessing&#91;.dummy&#93;</code> wrapper</h2>
<p>Yes, you read it right, this is the same section as last fortnight&#39;s blog. My mentor Pradyun Gedam gave me a green light to have <a href=https://github.com/pypa/pip/pull/8411>GH-8411</a> merged without support for Python 2 and the non-lazy map variant, which turns out to be troublesome for multithreading.</p>
<p>The tests still needs to pass of course and the flaky tests &#40;see failing tests over Azure Pipeline in the past&#41; really gave me a panic attack earlier today. We probably need to mark them as xfail or investigate why they are undeterministic specifically on Azure, but the real reason I was <em>all caught up and confused</em> was that the unit tests I added mess with the cached imports and as <code>pip</code>&#39;s tests are run in parallel, who knows what it might affect. I was so relieved to not discover any new set of tests made flaky by ones I&#39;m trying to add&#33;</p>
<h2 id="the_file-like_object_mapping_zip_over_http">The file-like object mapping ZIP over HTTP</h2>
<p>This is where the fun starts.  Before we dive in, let&#39;s recall some background information on this.  As discovered by Danny McClanahan in <a href=https://github.com/pypa/pip/pull/7819>GH-7819</a>, it is possible to only download a potion of a wheel and it&#39;s still valid for <code>pip</code> to get the distribution&#39;s metadata. In the same thread, Daniel Holth suggested that one may use HTTP range requests to specifically ask for the tail of the wheel, where the ZIP&#39;s central directory record as well as where usually <code>dist-info</code> &#40;the directory containing <code>METADATA</code>&#41; can be found.</p>
<p>Well, <em>usually</em>.  While <a href=https://www.python.org/dev/peps/pep-0427>PEP 427</a> does indeed recommend</p>
<blockquote>
<p>Archivers are encouraged to place the <code>.dist-info</code> files physically at the end of the archive.  This enables some potentially interesting ZIP tricks including the ability to amend the metadata without rewriting the entire archive.</p>
</blockquote>
<p>one of the mentioned <em>tricks</em> is adding shared libraries to wheels of extension modules &#40;using e.g. <code>auditwheel</code> or <code>delocate</code>&#41;. Thus for non-pure Python wheels, it is unlikely that the metadata lie in the last few megabytes.  Ignoring source distributions is bad enough, we can&#39;t afford making an optimization that doesn&#39;t work for extension modules, which are still an integral part of the Python ecosystem &#41;-:</p>
<p>But hey, the ZIP&#39;s directory record is warrantied to be at the end of the file&#33; Couldn&#39;t we do something about that?  The short answer is yes.  The long answer is, well, yessssssss&#33; That, plus magic provided by most operating systems, this is what we figured out:</p>
<ol>
<li><p>We can download a realatively small chunk at the end of the wheel until it is recognizable as a valid ZIP file.</p>
</li>
<li><p>In order for the end of the archive to actually appear as the end to <code>zipfile</code>, we feed to it an object with <code>seek</code> and <code>read</code> defined. As navigating to the rear of the file is performed by calling <code>seek</code> with relative offset and <code>whence&#61;SEEK_END</code> &#40;see <code>man 3 fseek</code> for more details&#41;, we are completely able to make the wheels in the cloud to behave as if it were available locally.</p>
<p><img src="https://cnx.gdn/assets/cloud.gif" alt="Wheel in the cloud" /></p>
</li>
<li><p>For large wheels, it is better to store them in hard disks instead of memory. For smaller ones, it is also preferable to store it as a file to avoid &#40;error-prony and often not really efficient&#41; manual tracking and joining of downloaded segments.  We only use a small potion of the wheel, however just in case one is wonderring, we have very little control over when <code>tempfile.SpooledTemporaryFile</code> rolls over, so the memory-disk hybrid is not exactly working as expected.</p>
</li>
<li><p>With all these in mind, all we have to do is to define an intermediate object check for local availability and download if needed on calls to <code>read</code>, to lazily provide the data over HTTP and reduce execution time.</p>
</li>
</ol>
<p>The only theoretical challenge left is to keep track of downloaded intervals, which I finally figured out after a few trials and errors.  The code was submitted as a pull request to <code>pip</code> at <a href=https://github.com/pypa/pip/pull/8467>GH-8467</a>.  A more modern &#40;read: Python 3-only&#41; variant was packaged and uploaded to PyPI under the name of lazip_.  I am unaware of any use case for it outside of <code>pip</code>, but it&#39;s certainly fun to play with d-:</p>
<h2 id="whats_next">What&#39;s next?</h2>
<p>I have been falling short of getting the PRs mention above merged for quite a while.  With <code>pip</code>&#39;s next beta coming really soon, I have to somehow make the patches reach a certain standard and enough attention to be part of the pre-release—beta-testing would greatly help the success of the GSoC project. To other GSoC students and mentors reading this, I also hope your projects to turn out successful&#33;</p>
    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/2020/gsoc/article/2@cnx%3E&Subject=Re: The Wonderful Wizard of O&#39;zip">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/2020/gsoc/article/2@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/2020/gsoc/article/2/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Unexpected Things When You&#39;re Expecting</title>
  <link>https://cnx.gdn/blog/2020/gsoc/article/1/index.html</link>
  <guid>https://cnx.gdn/blog/2020/gsoc/article/1/index.html</guid>
  <description>GSoC 2020: Unexpected Things When You&#39;re Expecting</description>
  <category>gsoc</category><category>pip</category><category>python</category>
  <pubDate>Tue, 09 Jun 2020 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="unexpected_things_when_youre_expecting">Unexpected Things When You&#39;re Expecting</h1>
<p>Hi everyone, I hope that you are all doing well and wishes you all good health&#33; The last week has not been really kind to me with a decent amount of academic pressure &#40;my school year is lasting until early Jully&#41;. It would be bold to say that I have spent 10 hours working on my GSoC project since the last check-in, let alone the 30 hours per week requirement. That being said, there were still some discoveries that I wish to share.</p>
<div class="franklin-toc"><ol><li>The <code>multiprocessing&#91;.dummy&#93;</code> wrapper</li><li>The change in direction</li></ol></div>
<h2 id="the_multiprocessingdummy_wrapper">The <code>multiprocessing&#91;.dummy&#93;</code> wrapper</h2>
<p>Most of the time I spent was to finalize the multi&#123;processing,threading&#125; wrapper for <code>map</code> function that submit tasks to the worker pool. To my surprise, it is rather difficult to write something that is not only portable but also easy to read and test.</p>
<p>By <a href=https://github.com/pypa/pip/pull/8320>the latest commit</a>, I realized the following:</p>
<ol>
<li><p>The <code>multiprocessing</code> module was not designed for the implementation details to be abstracted away entirely.  For example, the lazy <code>map</code>&#39;s could be really slow without specifying suitable chunk size &#40;to cut the input iterable and distribute them to workers in the pool&#41;. By <em>suitable</em>, I mean only an order smaller than the input.  This defeats half of the purpose of making it lazy: allowing the input to be evaluated lazily.  Luckily, in the use case I&#39;m aiming for, the length of the iterable argument is small and the laziness is only needed for the output &#40;to pipeline download and installation&#41;.</p>
</li>
<li><p>Mocking <code>import</code> for testing purposes can never be pretty.  One reason is that we &#40;Python users&#41; have very little control over the calls of <code>import</code> statements and its lower-level implementation <code>__import__</code>. In order to properly patch this built-in function, unlike for others of the same group, we have to <code>monkeypatch</code> the name from <code>builtins</code> &#40;or <code>__builtins__</code> under Python 2&#41; instead of the module that import stuff. Furthermore, because of the special namespacing, to avoid infinite recursion we need to alias the function to a different name for fallback.</p>
</li>
<li><p>To add to the problem, <code>multiprocessing</code> lazily imports the fragile module during pools creation.  Since the failure is platform-specific &#40;the lack of <code>sem_open</code>&#41;, it was decided to check upon the import of the <code>pip</code>&#39;s module.  Although the behavior is easier to reason in human language, testing it requires invalidating cached import and re-import the wrapper module.</p>
</li>
<li><p>Last but not least, I now understand the pain of keeping Python 2 compatibility that many package maintainers still need to deal with everyday &#40;although Python 2 has reached its end-of-life, <code>pip</code>, for example, <a href=https://github.com/pypa/pip/pull/6148>will still support it for another year</a>&#41;.</p>
</li>
</ol>
<h2 id="the_change_in_direction">The change in direction</h2>
<p>Since last week, my mentor Pradyun Gedam and I set up weekly real-time meeting &#40;a fancy term for video/audio chat in the worldwide quarantine era&#41; for the entire GSoC period. During the last session, we decided to put parallelization of download during resolution on hold, in favor of a more beneficial goal: <a href=https://github.com/pypa/pip/pull/7819>partially download the wheels during
dependency resolution</a>.</p>
<p><img src="https://cnx.gdn/assets/swirl.png" alt="" /></p>
<p>As discussed by Danny McClanahan and the maintainers of <code>pip</code>, it is feasible to only download a few kB of a wheel to obtain enough metadata for the resolution of dependency.  While this is only applicable to wheels &#40;i.e. prebuilt packages&#41;, other packaging format only make up less than 20&#37; of the downloads &#40;at least on PyPI&#41;, and the figure is much less for the most popular packages.  Therefore, this optimization alone could make <a href="https://www.ei8fdb.org/test-pips-alpha-resolver-and-help-us-document-dependency-conflicts">the upcoming backtracking resolver</a>&#39;s performance par with the legacy one.</p>
<p>During the last few years, there has been a lot of effort being poured into replacing <code>pip</code>&#39;s current resolver that is unable to resolve conflicts. While its correctness will be ensured by some of the most talented and hard-working developers in the Python packaging community, from the users&#39; point of view, it would be better to have its performance not lagging behind the old one.  Aside from the increase in CPU cycles for more rigorous resolution, more I/O, especially networking operations is expected to be performed.  This is due to <a href=https://github.com/pypa/pip/pull/7406#issuecomment-583891169>the lack
of a standard and efficient way to acquire the metadata</a>.  Therefore, unlike most package managers we are familiar with, <code>pip</code> has to fetch &#40;and possibly build&#41; the packages solely for dependency informations.</p>
<p>Fortunately, <a href=https://www.python.org/dev/peps/pep-0427#recommended-archiver-features>PEP 427#recommended-archiver-features</a> recommends package builders to place the metadata at the end of the archive. This allows the resolver to only fetch the last few kB using <code>HTTP range requests</code>_ for the relevant information. Simply appending <code>Range: bytes&#61;-8000</code> to the request header in <code>pip._internal.network.download</code> makes the resolution process <em>lightning</em> fast.  Of course this breaks the installation but I am confident that it is not difficult to implement this optimization cleanly.</p>
<p>One drawback of this optimization is the compatibility.  Not every Python package index support range requests, and it is not possible to verify the partial wheel.  While the first case is unavoidable, for the other, hashes checking is usually used for pinned/locked-version requirements, thus no backtracking is done during dependency resolution.</p>
<p>Either way, before installation, the packages selected by the resolver can be downloaded in parallel.  This warranties a larger crowd of packages, compared to parallelization during resolution, where the number of downloads can be as low as one during trail of different versions of the same package.</p>
<p>Unfortunately, I have not been able to do much other than <a href=https://github.com/pypa/pip/pull/8411>a minor clean up</a>.  I am looking forward to accomplishing more this week and seeing what this path will lead us too&#33;  At the moment, I am happy that I&#39;m able to meet the blog deadline, at least in UTC&#33;</p>
<p></p>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/2020/gsoc/article/1@cnx%3E&Subject=Re: Unexpected Things When You&#39;re Expecting">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/2020/gsoc/article/1@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/2020/gsoc/article/1/comments.xml</wfw:commentRss>
</item>
<item>
  <title>System Cascade Connection</title>
  <link>https://cnx.gdn/blog/system/index.html</link>
  <guid>https://cnx.gdn/blog/system/index.html</guid>
  <description>Properties of cascade connected systems analyzed via anonymous functions</description>
  <category>fun</category><category>math</category>
  <pubDate>Wed, 15 Apr 2020 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1 id="system_cascade_connection">System Cascade Connection</h1>
<p>Given two discrete-time systems \(A\) and \(B\) connected in cascade to form a new system \(C = x \mapsto B(A(x))\), we examine the following properties:</p>
<div class="franklin-toc"><ol><li>Linearity</li><li>Time Invariance</li><li>LTI Ordering</li><li>Causality</li><li>BIBO Stability</li></ol></div>
<h2 id="linearity">Linearity</h2>
<p>If \(A\) and \(B\) are linear, i.e. for all signals \(x_i\) and scalars \(a_i\),</p>
\[\begin{aligned}
  A\left(n \mapsto \sum_i a_i x_i[n]\right) = n \mapsto \sum_i a_i A(x_i)[n]\\
  B\left(n \mapsto \sum_i a_i x_i[n]\right) = n \mapsto \sum_i a_i B(x_i)[n]
\end{aligned}\]
<p>then \(C\) is also linear</p>
\[\begin{aligned}
  C\left(n \mapsto \sum_i a_i x_i[n]\right)
  &= B\left(A\left(n \mapsto \sum_i a_i x_i[n]\right)\right)\\
  &= B\left(n \mapsto \sum_i a_i A(x_i)[n]\right)\\
  &= n \mapsto \sum_i a_i B(A(x_i))[n]\\
  &= n \mapsto \sum_i a_i C(x_i)[n]
\end{aligned}\]
<h2 id="time_invariance">Time Invariance</h2>
<p>If \(A\) and \(B\) are time invariant, i.e. for all signals \(x\) and integers \(k\),</p>
\[\begin{aligned}
  A(n \mapsto x[n - k]) &= n \mapsto A(x)[n - k]\\
  B(n \mapsto x[n - k]) &= n \mapsto B(x)[n - k]
\end{aligned}\]
<p>then \(C\) is also time invariant</p>
\[\begin{aligned}
  C(n \mapsto x[n - k])
  &= B(A(n \mapsto x[n - k]))\\
  &= B(n \mapsto A(x)[n - k])\\
  &= n \mapsto B(A(x))[n - k]\\
  &= n \mapsto C(x)[n - k]
\end{aligned}\]
<h2 id="lti_ordering">LTI Ordering</h2>
<p>If \(A\) and \(B\) are linear and time-invariant, there exists signals \(g\) and \(h\) such that for all signals \(x\), \(A = x \mapsto x * g\) and \(B = x \mapsto x * h\), thus </p>
\[B(A(x)) = B(x * g) = x * g * h = x * h * g = A(x * h) = A(B(x))\]
<p>or interchanging \(A\) and \(B\) order does not change \(C\).</p>
<h2 id="causality">Causality</h2>
<p>If \(A\) and \(B\) are causal, i.e. for all signals \(x\), \(y\) and any choise of integer \(k\),</p>
\[\begin{aligned}
  \forall n < k, x[n] = y[n]\quad
  \Longrightarrow &\;\begin{cases}
  \forall n < k, A(x)[n] = A(y)[n]\\
  \forall n < k, B(x)[n] = B(y)[n]
  \end{cases}\\
  \Longrightarrow &\;\forall n < k, B(A(x))[n] = B(A(y))[n]\\
  \Longleftrightarrow &\;\forall n < k, C(x)[n] = C(y)[n]
\end{aligned}\]
<p>then \(C\) is also causal.</p>
<h2 id="bibo_stability">BIBO Stability</h2>
<p>If \(A\) and \(B\) are stable, i.e. there exists a signal \(x\) and scalars \(a\) and \(b\) that for all integers \(n\),</p>
\[\begin{aligned}
  |x[n]| < a &\Longrightarrow |A(x)[n]| < b\\
  |x[n]| < a &\Longrightarrow |B(x)[n]| < b
\end{aligned}\]
<p>then \(C\) is also stable, i.e. there exists a signal \(x\) and scalars \(a\), \(b\) and \(c\) that for all integers \(n\),</p>
\[\begin{aligned}
  |x[n]| < a\quad
  \Longrightarrow &\;|A(x)[n]| < b\\
  \Longrightarrow &\;|B(A(x))[n]| < c\\
  \Longleftrightarrow &\;|C(x)[n]| < c
\end{aligned}\]    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/system@cnx%3E&Subject=Re: System Cascade Connection">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/system@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/system/comments.xml</wfw:commentRss>
</item>
<item>
  <title>Infinite Sequences: A Case Study in Functional Python</title>
  <link>https://cnx.gdn/blog/conseq/index.html</link>
  <guid>https://cnx.gdn/blog/conseq/index.html</guid>
  <description>SICP subsection 3.5.2 in Python</description>
  <category>fun</category><category>math</category><category>python</category>
  <pubDate>Thu, 28 Feb 2019 00:00:00 +0000</pubDate>
  <content:encoded><![CDATA[
<h1>Infinite Sequences: A Case Study in Functional Python</h1>
<p>In this article, we will only consider sequences defined by a function whose domain is a subset of the set of all integers.  Such sequences will be <em>visualized</em>, i.e. we will try to evaluate the first few &#40;thousand&#41; elements, using functional programming paradigm, where functions are more similar to the ones in math &#40;in contrast to imperative style with side effects confusing to inexperenced coders&#41;.  The idea is taken from <a href="https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-24.html#&#37;_sec_3.5.2">subsection 3.5.2 of SICP</a> and adapted to Python, which, compare to Scheme, is significantly more popular: Python is pre-installed on almost every modern Unix-like system, namely macOS, GNU/Linux and the &#42;BSDs; and even at MIT, the new 6.01 in Python has recently replaced the legendary 6.001 &#40;SICP&#41;.</p>
<p>One notable advantage of using Python is its huge <strong>standard</strong> library. For example the <em>identity sequence</em> &#40;sequence defined by the identity function&#41; can be imported directly from <code>itertools</code>:</p>
<pre><code class="language-python">&gt;&gt;&gt; from itertools import count
&gt;&gt;&gt; positive_integers &#61; count&#40;start&#61;1&#41;
&gt;&gt;&gt; next&#40;positive_integers&#41;
1
&gt;&gt;&gt; next&#40;positive_integers&#41;
2
&gt;&gt;&gt; for _ in range&#40;4&#41;: next&#40;positive_integers&#41;
... 
3
4
5
6</code></pre>
<p>To open a Python emulator, simply lauch your terminal and run <code>python</code>. If that is somehow still too struggling, navigate to <a href="https://www.python.org/shell">the interactive shell</a> on Python.org.</p>
<p><em>Let&#39;s get it started</em> with somethings everyone hates: recursively defined sequences, e.g. the famous Fibonacci &#40;\(F_n = F_{n-1} + F_{n-2}\), \(F_1 = 1\) and \(F_0 = 0\)&#41;.  Since <a href="https://neopythonic.blogspot.com/2009/04/final-words-on-tail-calls.html">Python does not support</a> <a href="https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-11.html#call_footnote_Temp_48">tail recursion</a>, it&#39;s generally <strong>not</strong> a good idea to define anything recursively &#40;which is, ironically, the only trivial <em>functional</em> solution in this case&#41; but since we will only evaluate the first few terms &#40;use the <strong>Tab</strong> key to indent the line when needed&#41;:</p>
<pre><code class="language-python">&gt;&gt;&gt; def fibonacci&#40;n, a&#61;0, b&#61;1&#41;:
...     # To avoid making the code look complicated,
...     # n &lt; 0 is not handled here.
...     return a if n &#61;&#61; 0 else fibonacci&#40;n - 1, b, a &#43; b&#41;
... 
&gt;&gt;&gt; fibo_seq &#61; &#40;fibonacci&#40;n&#41; for n in count&#40;start&#61;0&#41;&#41;
&gt;&gt;&gt; for _ in range&#40;7&#41;: next&#40;fibo_seq&#41;
... 
0
1
1
2
3
5
8</code></pre>
<div class="admonition note"><p class="admonition-title">Note</p><p>The <code>fibo_seq</code> above is just to demonstrate how <code>itertools.count</code> can be use to create an infinite sequence defined by a function. For better performance, the following should be used instead:</p>
<pre><code class="language-python">def fibonacci_sequence&#40;a&#61;0, b&#61;1&#41;:
    yield a
    yield from fibonacci_sequence&#40;b, a&#43;b&#41;</code></pre>
</div>
<p>It is noticable that the elements having been iterated through &#40;using <code>next</code>&#41; will disappear forever in the void &#40;oh no&#33;&#41;, but that is the cost we are willing to pay to save some memory, especially when we need to evaluate a member of &#40;arbitrarily&#41; large index to estimate the sequence&#39;s limit. One case in point is estimating a definite integral using <a href="https://en.wikipedia.org/wiki/Riemann_sum#Left_Riemann_sum">left Riemann sum</a>.</p>
<pre><code class="language-python">def integral&#40;f, a, b&#41;:
    def left_riemann_sum&#40;n&#41;:
        dx &#61; &#40;b-a&#41; / n
        def x&#40;i&#41;: return a &#43; i*dx
        return sum&#40;f&#40;x&#40;i&#41;&#41; for i in range&#40;n&#41;&#41; * dx
    return left_riemann_sum</code></pre>
<p>The function <code>integral&#40;f, a, b&#41;</code> as defined above returns a function taking \(n\) as an argument.  As \(n\to\infty\), its result approaches \(\int_a^b f(x)\mathrm d x\).  For example, we are going to estimate \(\pi\) as the area of a semicircle whose radius is \(\sqrt 2\):</p>
<pre><code class="language-python">&gt;&gt;&gt; from math import sqrt
&gt;&gt;&gt; def semicircle&#40;x&#41;: return sqrt&#40;abs&#40;2 - x*x&#41;&#41;
... 
&gt;&gt;&gt; pi &#61; integral&#40;semicircle, -sqrt&#40;2&#41;, sqrt&#40;2&#41;&#41;
&gt;&gt;&gt; pi_seq &#61; &#40;pi&#40;n&#41; for n in count&#40;start&#61;2&#41;&#41;
&gt;&gt;&gt; for _ in range&#40;3&#41;: next&#40;pi_seq&#41;
... 
2.000000029802323
2.514157464087051
2.7320508224700384</code></pre>
<p>Whilst the first few aren&#39;t quite close, at index around 1000, the result is somewhat acceptable:</p>
<pre><code class="language-julia">3.1414873191059525
3.1414874770617427
3.1414876346231577</code></pre>
<p>Since we are comfortable with sequence of sums, let&#39;s move on to sums of a sequence, which are called series.  For estimation, again, we are going to make use of infinite sequences of partial sums, which are implemented as <code>itertools.accumulate</code> by thoughtful Python developers.  <a href="https://en.wikipedia.org/wiki/Geometric_series">Geometric</a> and <a href="https://math.oregonstate.edu/home/programs/undergrad/CalculusQuestStudyGuides/SandS/SeriesTests/p-series.html">p-series</a> can be defined as follow:</p>
<pre><code class="language-python">from itertools import accumulate as partial_sumsdef geometric_series&#40;r, a&#61;1&#41;:
    return partial_sums&#40;a*r**n for n in count&#40;0&#41;&#41;def p_series&#40;p&#41;:
    return partial_sums&#40;1 / n**p for n in count&#40;1&#41;&#41;</code></pre>
<p>We can then use these to determine whether a series is convergent or divergent. For instance, one can easily verify that the \(p\)-series with \(p = 2\) converges to \(\pi^2 / 6 \approx 1.6449340668482264\) via</p>
<pre><code class="language-python">&gt;&gt;&gt; s &#61; p_series&#40;p&#61;2&#41;
&gt;&gt;&gt; for _ in range&#40;11&#41;: next&#40;s&#41;
... 
1.0
1.25
1.3611111111111112
1.4236111111111112
1.4636111111111112
1.4913888888888889
1.511797052154195
1.527422052154195
1.5397677311665408
1.5497677311665408
1.558032193976458</code></pre>
<p>We can observe that it takes quite a lot of steps to get the precision we would generally expect &#40;\(s_{11}\) is only precise to the first decimal place; second decimal places: \(s_{101}\); third: \(s_{2304}\)&#41;. Luckily, many techniques for series acceleration are available. <a href="https://en.wikipedia.org/wiki/Shanks_transformation">Shanks transformation</a> for instance, can be implemented as follow:</p>
<pre><code class="language-python">from itertools import islice, teedef shanks&#40;seq&#41;:
    return map&#40;lambda x, y, z: &#40;x*z - y*y&#41; / &#40;x &#43; z - y*2&#41;,
               *&#40;islice&#40;t, i, None&#41; for i, t in enumerate&#40;tee&#40;seq, 3&#41;&#41;&#41;&#41;</code></pre>
<p>In the code above, <code>lambda x, y, z: &#40;x*z - y*y&#41; / &#40;x &#43; z - y*2&#41;</code> denotes the anonymous function \((x, y, z) \mapsto \frac{xz - y^2}{x + z - 2y}\) and <code>map</code> is a higher order function applying that function to respective elements of subsequences starting from index 1, 2 and 3 of <code>seq</code>. On Python 2, one should import <code>imap</code> from <code>itertools</code> to get the same <a href="https://en.wikipedia.org/wiki/Lazy_evaluation">lazy</a> behavior of <code>map</code> on Python 3.</p>
<pre><code class="language-python">&gt;&gt;&gt; s &#61; shanks&#40;p_series&#40;2&#41;&#41;
&gt;&gt;&gt; for _ in range&#40;10&#41;: next&#40;s&#41;
... 
1.4500000000000002
1.503968253968257
1.53472222222223
1.5545202020202133
1.5683119658120213
1.57846371882088
1.5862455815659202
1.5923993101138652
1.5973867787856946
1.6015104548459742</code></pre>
<p>The result was quite satisfying, yet we can do one step futher by continuously applying the transformation to the sequence:</p>
<pre><code class="language-python">&gt;&gt;&gt; def compose&#40;transform, seq&#41;:
... 	yield next&#40;seq&#41;
... 	yield from compose&#40;transform, transform&#40;seq&#41;&#41;
... 
&gt;&gt;&gt; s &#61; compose&#40;shanks, p_series&#40;2&#41;&#41;
&gt;&gt;&gt; for _ in range&#40;10&#41;: next&#40;s&#41;
... 
1.0
1.503968253968257
1.5999812811165188
1.6284732442271674
1.6384666832276524
1.642311342667821
1.6425249569252578
1.640277484549416
1.6415443295058203
1.642038043478661</code></pre>
<p>Shanks transformation works on every sequence &#40;not just sequences of partial sums&#41;.  Back to previous example of using left Riemann sum to compute definite integral:</p>
<pre><code class="language-python">&gt;&gt;&gt; pi_seq &#61; compose&#40;shanks, map&#40;pi, count&#40;2&#41;&#41;&#41;
&gt;&gt;&gt; for _ in range&#40;10&#41;: next&#40;pi_seq&#41;
... 
2.000000029802323
2.978391111182236
3.105916845397819
3.1323116570377185
3.1389379264270736
3.140788413965646
3.140921512857936
3.1400282163913436
3.1400874774021816
3.1407097229603256
&gt;&gt;&gt; next&#40;islice&#40;pi_seq, 300, None&#41;&#41;
3.1415061302492413</code></pre>
<p>Now having series defined, let&#39;s see if we can learn anything about power series. Sequence of partial sums of power series \(\sum c_n (x - a)^n\) can be defined as</p>
<pre><code class="language-python">from operator import muldef power_series&#40;c, start&#61;0, a&#61;0&#41;:
    return lambda x: partial_sums&#40;map&#40;mul, c, &#40;x**n for n in count&#40;start&#41;&#41;&#41;&#41;</code></pre>
<p>We can use this to compute functions that can be written as <a href="https://en.wikipedia.org/wiki/Taylor_series">Taylor series</a>:</p>
<pre><code class="language-python">from math import factorial
def exp&#40;x&#41;:
    return power_series&#40;1/factorial&#40;n&#41; for n in count&#40;0&#41;&#41;&#40;x&#41;def cos&#40;x&#41;:
    c &#61; &#40;&#40;1 - n&#37;2&#41; * &#40;1 - n&#37;4&#41; / factorial&#40;n&#41; for n in count&#40;0&#41;&#41;
    return power_series&#40;c&#41;&#40;x&#41;def sin&#40;x&#41;:
    c &#61; &#40;n&#37;2 * &#40;2 - n&#37;4&#41; / factorial&#40;n&#41; for n in count&#40;1&#41;&#41;
    return power_series&#40;c, start&#61;1&#41;&#40;x&#41;</code></pre>
<p>Amazing&#33;  Let&#39;s test &#39;em&#33;</p>
<pre><code class="language-python">&gt;&gt;&gt; e &#61; compose&#40;shanks, exp&#40;1&#41;&#41; # this should converges to 2.718281828459045
&gt;&gt;&gt; for _ in range&#40;4&#41;: next&#40;e&#41;
... 
1.0
2.749999999999996
2.718276515152136
2.718281825486623</code></pre>
<p>Impressive, huh? For sine and cosine, series acceleration is not even necessary:</p>
<pre><code class="language-python">&gt;&gt;&gt; from math import pi as PI
&gt;&gt;&gt; s &#61; sin&#40;PI/6&#41;
&gt;&gt;&gt; for _ in range&#40;5&#41;: next&#40;s&#41;
... 
0.5235987755982988
0.5235987755982988
0.49967417939436376
0.49967417939436376
0.5000021325887924
&gt;&gt;&gt; next&#40;islice&#40;cos&#40;PI/3&#41;, 8, None&#41;&#41;
0.500000433432915</code></pre>
<p></p>    <a href="mailto:cnx.site@loa.loang.net?In-Reply-To=%3Cblog/conseq@cnx%3E&Subject=Re: Infinite Sequences: A Case Study in Functional Python">Reply via email</a>]]></content:encoded>
  <comments><![CDATA[https://lists.sr.ht/~cnx/site?search=In-Reply-To:%3Cblog/conseq@cnx%3E]]></comments>
  <wfw:commentRss>https://cnx.gdn/blog/conseq/comments.xml</wfw:commentRss>
</item>
</channel></rss>
