commit f55e549c2e9e35a66a2d4c8c69a8cfc032aafa70
parent be108d16b1b3e5d4d961404269e281ae89b26db5
Author: Beau <cbeauhilton@gmail.com>
Date: Thu, 1 Dec 2022 13:58:28 -0600
migrate to soupault
Diffstat:
46 files changed, 1705 insertions(+), 1400 deletions(-)
diff --git a/_footer.html b/_footer.html
@@ -1,9 +0,0 @@
-
-<br /><br /><br />
-<p><a href="/">home</a></p>
-
- <link rel="stylesheet" href="/prism.css">
- <script defer type="text/javascript" src="/js/tab-title.js"></script>
- <script defer type="text/javascript" src="/js/prism.js"></script>
-</body>
-
diff --git a/_header.html b/_header.html
@@ -1,16 +0,0 @@
-<!DOCTYPE html>
-<html lang="en" dir="ltr">
-<head>
- <meta charset="UTF-8">
- <link rel="alternate" type="application/atom+xml" href="/rss.xml">
- <link rel="stylesheet" href="/style.css">
- <link rel="icon" href="https://git.beauhilton.com/logo.png">
- <meta name="viewport" content="width=device-width, initial-scale=1">
- <meta name="theme-color" content="#70b433"/>
- <title id="title">.</title>
-</head>
-<body id="home">
-<section id="masthead">
- <h1><a href="/index.html" title="beau hilton">beau hilton</a></h1>
-</section>
-
diff --git a/about.md b/about.md
@@ -1,26 +0,0 @@
-I'm a husband, father, physician, educator, and data scientist.
-
-# Husband and father
-These are the most important roles I play.
-If we meet, and you're interested, we can talk about it.
-
-# Physician
-Medical school at Cleveland Clinic Lerner College of Medicine
-of Case Western Reserve University,
-Class of 2020.
-
-Harrison Society member at Vanderbilt University,
-which includes internal medicine residency (2020-2022)
-and research/fellowship in hematology-oncology (2022-2026).
-
-# Educator
-Harvard Macy Institute faculty, 2018-present.
-Health Care Education 2.0.
-
-# Data Scientist
-Machine learning and data science approaches to
-diagnosis and prognosis of blood cancers;
-healthcare disparities in hospital medicine;
-predictive modeling of hospital readmissions and length of stay.
-
-[Google Scholar profile](https://scholar.google.com/citations?user=Ng5AgXAAAAAJ)
diff --git a/feed.xml b/feed.xml
@@ -1,42 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
- <channel>
- <title>beauhilton.com</title>
- <link>https://beauhilton.com/</link>
- <atom:link href="https://beauhilton.com/feed.xml" rel="self" type="application/rss+xml" />
- <description>beau's personal site.</description>
- <language>en-us</language>
- <generator>my own two hands</generator>
- <managingEditor>beau@beauhilton.com (Beau Hilton)</managingEditor>
- <webMaster>beau@beauhilton.com (Beau Hilton)</webMaster>
- <lastBuildDate>2022-11-13T00:00:01Z</lastBuildDate>
- <item>
- <title>fix MIME Types to unbreak RSS feeds served by OpenBSD's httpd(8)</title>
- <link>https://beauhilton.com/posts/mime.html</link>
- <guid>https://beauhilton.com/posts/mime.html</guid>
- <pubDate>2022-11-13T15:04:26Z</pubDate>
- <description>Add more MIME Types to `httpd(8)` so RSS feed readers know what they're getting into.</description>
- </item>
- <item>
- <title>upgrade old OpenBSD installs</title>
- <link>https://beauhilton.com/posts/ugbsd.html</link>
- <guid>https://beauhilton.com/posts/ugbsd.html</guid>
- <pubDate>2022-11-11T13:04:26Z</pubDate>
- <description>Upgrade (possibly very) old versions of OpenBSD by finding a generous mirror.</description>
- </item>
- <item>
- <title>geocheatcode</title>
- <link>https://beauhilton.com/posts/geocheatcode.html</link>
- <guid>https://beauhilton.com/posts/geocheatcode.html</guid>
- <pubDate>2022-04-22T18:04:26Z</pubDate>
- <description>Get the latitude and longitude of badly formatted search strings using Google Maps and regex.</description>
- </item>
- <item>
- <title>Set up enterprise wifi on Arch Linux</title>
- <link>https://beauhilton.com/posts/employee-wifi.html</link>
- <guid>https://beauhilton.com/posts/employee-wifi.html</guid>
- <pubDate>2021-09-17T18:04:26Z</pubDate>
- <description>Almost works sometimes.</description>
- </item>
- </channel>
-</rss>
diff --git a/fonts.css b/fonts.css
@@ -1,95 +0,0 @@
-
-/* ibm-plex-mono-regular - latin-ext_latin */
-@font-face {
- font-family: 'IBM Plex Mono';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: local('IBM Plex Mono'), local('IBMPlexMono'),
- url('../fonts/ibm-plex-mono-v5-latin-ext_latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
- url('../fonts/ibm-plex-mono-v5-latin-ext_latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
-}
-
-/* ibm-plex-mono-italic - latin-ext_latin */
-@font-face {
- font-family: 'IBM Plex Mono';
- font-style: italic;
- font-weight: 400;
- font-display: swap;
- src: local('IBM Plex Mono Italic'), local('IBMPlexMono-Italic'),
- url('../fonts/ibm-plex-mono-v5-latin-ext_latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
- url('../fonts/ibm-plex-mono-v5-latin-ext_latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
-}
-
-/* ibm-plex-mono-600 - latin-ext_latin */
-@font-face {
- font-family: 'IBM Plex Mono';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: local('IBM Plex Mono SemiBold'), local('IBMPlexMono-SemiBold'),
- url('../fonts/ibm-plex-mono-v5-latin-ext_latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
- url('../fonts/ibm-plex-mono-v5-latin-ext_latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
-}
-
-/* ibm-plex-sans-regular - latin-ext_latin */
-@font-face {
- font-family: 'IBM Plex Sans';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: local('IBM Plex Sans'), local('IBMPlexSans'),
- url('../fonts/ibm-plex-sans-v7-latin-ext_latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
- url('../fonts/ibm-plex-sans-v7-latin-ext_latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
-}
-/* ibm-plex-sans-italic - latin-ext_latin */
-@font-face {
- font-family: 'IBM Plex Sans';
- font-style: italic;
- font-weight: 400;
- font-display: swap;
- src: local('IBM Plex Sans Italic'), local('IBMPlexSans-Italic'),
- url('../fonts/ibm-plex-sans-v7-latin-ext_latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
- url('../fonts/ibm-plex-sans-v7-latin-ext_latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
-}
-/* ibm-plex-sans-600 - latin-ext_latin */
-@font-face {
- font-family: 'IBM Plex Sans';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: local('IBM Plex Sans SemiBold'), local('IBMPlexSans-SemiBold'),
- url('../fonts/ibm-plex-sans-v7-latin-ext_latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
- url('../fonts/ibm-plex-sans-v7-latin-ext_latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
-}
-/* ibm-plex-serif-regular - latin-ext_latin */
-@font-face {
- font-family: 'IBM Plex Serif';
- font-style: normal;
- font-weight: 400;
- font-display: swap;
- src: local('IBM Plex Serif'), local('IBMPlexSerif'),
- url('../fonts/ibm-plex-serif-v8-latin-ext_latin-regular.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
- url('../fonts/ibm-plex-serif-v8-latin-ext_latin-regular.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
-}
-/* ibm-plex-serif-italic - latin-ext_latin */
-@font-face {
- font-family: 'IBM Plex Serif';
- font-style: italic;
- font-weight: 400;
- font-display: swap;
- src: local('IBM Plex Serif Italic'), local('IBMPlexSerif-Italic'),
- url('../fonts/ibm-plex-serif-v8-latin-ext_latin-italic.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
- url('../fonts/ibm-plex-serif-v8-latin-ext_latin-italic.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
-}
-/* ibm-plex-serif-600 - latin-ext_latin */
-@font-face {
- font-family: 'IBM Plex Serif';
- font-style: normal;
- font-weight: 600;
- font-display: swap;
- src: local('IBM Plex Serif SemiBold'), local('IBMPlexSerif-SemiBold'),
- url('../fonts/ibm-plex-serif-v8-latin-ext_latin-600.woff2') format('woff2'), /* Chrome 26+, Opera 23+, Firefox 39+ */
- url('../fonts/ibm-plex-serif-v8-latin-ext_latin-600.woff') format('woff'); /* Chrome 6+, Firefox 3.6+, IE 9+, Safari 5.1+ */
-}
-
diff --git a/helpers/cmark-code-blocks.lua b/helpers/cmark-code-blocks.lua
@@ -0,0 +1,28 @@
+-- The CommonMark spec suggests that
+-- "```somelang" should produce <code class="language-somelang">
+--
+-- That's what most CommonMark implementation do.
+-- Pandoc, however, outputs <code class="somelang">
+--
+-- This makes using external highlighters much more difficult
+-- since you cannot match all code blocks with a known language
+-- using a single CSS selector.
+--
+-- This filter takes over the code block rendering process
+-- to produce CommonMark-style output.
+
+function CodeBlock(block)
+ if FORMAT:match 'html' then
+ local lang_attr = ""
+ if (#block.classes > 0) then
+ lang_attr = string.format([[class="language-%s"]], block.classes[1])
+ else
+ -- Ignore code blocks where language is not specified
+ end
+
+ local code = block.text:gsub("&", "&"):gsub("<", "<"):gsub(">", ">")
+
+ local html = string.format('<pre><code %s>%s</code></pre>', lang_attr, code)
+ return pandoc.RawBlock('html', html)
+ end
+end
diff --git a/img/logo.png b/img/logo.png
Binary files differ.
diff --git a/index.json b/index.json
@@ -0,0 +1,62 @@
+[
+ {
+ "url": "/posts/mime",
+ "page_file": "site/posts/mime.md",
+ "nav_path": [
+ "posts"
+ ],
+ "excerpt": "I've been getting back into RSS lately.\nTurns out, my own RSS feed was broken.",
+ "date": "2022-11-13",
+ "title": "fix MIME Types to unbreak RSS feeds served by OpenBSD’s\nhttpd(8)"
+ },
+ {
+ "url": "/posts/ugbsd",
+ "page_file": "site/posts/ugbsd.md",
+ "nav_path": [
+ "posts"
+ ],
+ "excerpt": "First of all, don't do how I do. \nUpgrade your installs regularly. \nOpenBSD makes it very easy.",
+ "date": "2022-11-11",
+ "title": "Upgrading out-of-date OpenBSD installs"
+ },
+ {
+ "url": "/posts/geocheatcode",
+ "page_file": "site/posts/geocheatcode.md",
+ "nav_path": [
+ "posts"
+ ],
+ "excerpt": "Here is background and code\nfor a trick I use to get\nGoogle to give me best-in-class guesses \nfor latitude and longitude,\ndespite goofy and/or downright bad location searches.",
+ "date": "2022-04-22",
+ "title": "geocheatcode"
+ },
+ {
+ "url": "/posts/intake-2022-03-25",
+ "page_file": "site/posts/intake-2022-03-25.md",
+ "nav_path": [
+ "posts"
+ ],
+ "excerpt": "candidal esophagitis, achalasia, H Pylori PUD",
+ "date": "2022-03-25",
+ "title": "intake"
+ },
+ {
+ "url": "/posts/employee-wifi",
+ "page_file": "site/posts/employee-wifi.md",
+ "nav_path": [
+ "posts"
+ ],
+ "excerpt": "Most big institutions have guest and employee wifi networks.\nGuest wifi is usually fine, fast enough for the basics,\nbut far inferior to employee wifi.\nOn a custom-built OS, such as a fairly minimalist Linux distribution, \ngetting the employee wifi to work\ncan be a beast.",
+ "date": "2021-09-17",
+ "title": "Set Up Enterprise Wifi on Arch Linux"
+ },
+ {
+ "url": "/posts/mr-2021",
+ "page_file": "site/posts/mr-2021.md",
+ "nav_path": [
+ "posts"
+ ],
+ "excerpt": "Diagnosis is... MDS/MPN/MF NOS. \ni.e., who knows.",
+ "date": "2021-08-23",
+ "title": "Morning Report 08/23/2021"
+ }
+]
+\ No newline at end of file
diff --git a/index.md b/index.md
@@ -1,9 +0,0 @@
-##### [about](/about.html)
-##### [now](/now.html)
-<!-- ## [info](/info.html) -->
-##### [posts](/posts.html)
-##### [notes](https://notes.beauhilton.com)
-##### [talks](https://talks.beauhilton.com)
-##### [git](https://git.beauhilton.com)
-##### [contact](/contact.html)
-##### [RSS](/feed.xml)
diff --git a/info.md b/info.md
@@ -1,3 +0,0 @@
-[Google Scholar](https://scholar.google.com/citations?user=Ng5AgXAAAAAJ)
-[ORCiD](https://orcid.org/0000-0002-1363-7452)
-[NPI](https://npiprofile.com/npi/1043830342)
diff --git a/js/prism.js b/js/prism.js
@@ -1,14 +0,0 @@
-/* PrismJS 1.25.0
-https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+bash+git+python+r+toml&plugins=toolbar+copy-to-clipboard */
-var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,e={},M={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof W?new W(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/</g,"<").replace(/\u00a0/g," ")},type:function(e){return Object.prototype.toString.call(e).slice(8,-1)},objId:function(e){return e.__id||Object.defineProperty(e,"__id",{value:++n}),e.__id},clone:function t(e,r){var a,n;switch(r=r||{},M.util.type(e)){case"Object":if(n=M.util.objId(e),r[n])return r[n];for(var i in a={},r[n]=a,e)e.hasOwnProperty(i)&&(a[i]=t(e[i],r));return a;case"Array":return n=M.util.objId(e),r[n]?r[n]:(a=[],r[n]=a,e.forEach(function(e,n){a[n]=t(e,r)}),a);default:return e}},getLanguage:function(e){for(;e&&!c.test(e.className);)e=e.parentElement;return e?(e.className.match(c)||[,"none"])[1].toLowerCase():"none"},currentScript:function(){if("undefined"==typeof document)return null;if("currentScript"in document)return document.currentScript;try{throw new Error}catch(e){var n=(/at [^(\r\n]*\((.*):[^:]+:[^:]+\)$/i.exec(e.stack)||[])[1];if(n){var t=document.getElementsByTagName("script");for(var r in t)if(t[r].src==n)return t[r]}return null}},isActive:function(e,n,t){for(var r="no-"+n;e;){var a=e.classList;if(a.contains(n))return!0;if(a.contains(r))return!1;e=e.parentElement}return!!t}},languages:{plain:e,plaintext:e,text:e,txt:e,extend:function(e,n){var t=M.util.clone(M.languages[e]);for(var r in n)t[r]=n[r];return t},insertBefore:function(t,e,n,r){var a=(r=r||M.languages)[t],i={};for(var l in a)if(a.hasOwnProperty(l)){if(l==e)for(var o in n)n.hasOwnProperty(o)&&(i[o]=n[o]);n.hasOwnProperty(l)||(i[l]=a[l])}var s=r[t];return r[t]=i,M.languages.DFS(M.languages,function(e,n){n===s&&e!=t&&(this[e]=i)}),i},DFS:function e(n,t,r,a){a=a||{};var i=M.util.objId;for(var l in n)if(n.hasOwnProperty(l)){t.call(n,l,n[l],r||l);var o=n[l],s=M.util.type(o);"Object"!==s||a[i(o)]?"Array"!==s||a[i(o)]||(a[i(o)]=!0,e(o,t,l,a)):(a[i(o)]=!0,e(o,t,null,a))}}},plugins:{},highlightAll:function(e,n){M.highlightAllUnder(document,e,n)},highlightAllUnder:function(e,n,t){var r={callback:t,container:e,selector:'code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code'};M.hooks.run("before-highlightall",r),r.elements=Array.prototype.slice.apply(r.container.querySelectorAll(r.selector)),M.hooks.run("before-all-elements-highlight",r);for(var a,i=0;a=r.elements[i++];)M.highlightElement(a,!0===n,r.callback)},highlightElement:function(e,n,t){var r=M.util.getLanguage(e),a=M.languages[r];e.className=e.className.replace(c,"").replace(/\s+/g," ")+" language-"+r;var i=e.parentElement;i&&"pre"===i.nodeName.toLowerCase()&&(i.className=i.className.replace(c,"").replace(/\s+/g," ")+" language-"+r);var l={element:e,language:r,grammar:a,code:e.textContent};function o(e){l.highlightedCode=e,M.hooks.run("before-insert",l),l.element.innerHTML=l.highlightedCode,M.hooks.run("after-highlight",l),M.hooks.run("complete",l),t&&t.call(l.element)}if(M.hooks.run("before-sanity-check",l),(i=l.element.parentElement)&&"pre"===i.nodeName.toLowerCase()&&!i.hasAttribute("tabindex")&&i.setAttribute("tabindex","0"),!l.code)return M.hooks.run("complete",l),void(t&&t.call(l.element));if(M.hooks.run("before-highlight",l),l.grammar)if(n&&u.Worker){var s=new Worker(M.filename);s.onmessage=function(e){o(e.data)},s.postMessage(JSON.stringify({language:l.language,code:l.code,immediateClose:!0}))}else o(M.highlight(l.code,l.grammar,l.language));else o(M.util.encode(l.code))},highlight:function(e,n,t){var r={code:e,grammar:n,language:t};return M.hooks.run("before-tokenize",r),r.tokens=M.tokenize(r.code,r.grammar),M.hooks.run("after-tokenize",r),W.stringify(M.util.encode(r.tokens),r.language)},tokenize:function(e,n){var t=n.rest;if(t){for(var r in t)n[r]=t[r];delete n.rest}var a=new i;return I(a,a.head,e),function e(n,t,r,a,i,l){for(var o in r)if(r.hasOwnProperty(o)&&r[o]){var s=r[o];s=Array.isArray(s)?s:[s];for(var u=0;u<s.length;++u){if(l&&l.cause==o+","+u)return;var c=s[u],g=c.inside,f=!!c.lookbehind,h=!!c.greedy,d=c.alias;if(h&&!c.pattern.global){var p=c.pattern.toString().match(/[imsuy]*$/)[0];c.pattern=RegExp(c.pattern.source,p+"g")}for(var v=c.pattern||c,m=a.next,y=i;m!==t.tail&&!(l&&y>=l.reach);y+=m.value.length,m=m.next){var b=m.value;if(t.length>n.length)return;if(!(b instanceof W)){var k,x=1;if(h){if(!(k=z(v,y,n,f)))break;var w=k.index,A=k.index+k[0].length,P=y;for(P+=m.value.length;P<=w;)m=m.next,P+=m.value.length;if(P-=m.value.length,y=P,m.value instanceof W)continue;for(var E=m;E!==t.tail&&(P<A||"string"==typeof E.value);E=E.next)x++,P+=E.value.length;x--,b=n.slice(y,P),k.index-=y}else if(!(k=z(v,0,b,f)))continue;var w=k.index,S=k[0],O=b.slice(0,w),L=b.slice(w+S.length),N=y+b.length;l&&N>l.reach&&(l.reach=N);var j=m.prev;O&&(j=I(t,j,O),y+=O.length),q(t,j,x);var C=new W(o,g?M.tokenize(S,g):S,d,S);if(m=I(t,j,C),L&&I(t,m,L),1<x){var _={cause:o+","+u,reach:N};e(n,t,r,m.prev,y,_),l&&_.reach>l.reach&&(l.reach=_.reach)}}}}}}(e,a,n,a.head,0),function(e){var n=[],t=e.head.next;for(;t!==e.tail;)n.push(t.value),t=t.next;return n}(a)},hooks:{all:{},add:function(e,n){var t=M.hooks.all;t[e]=t[e]||[],t[e].push(n)},run:function(e,n){var t=M.hooks.all[e];if(t&&t.length)for(var r,a=0;r=t[a++];)r(n)}},Token:W};function W(e,n,t,r){this.type=e,this.content=n,this.alias=t,this.length=0|(r||"").length}function z(e,n,t,r){e.lastIndex=n;var a=e.exec(t);if(a&&r&&a[1]){var i=a[1].length;a.index+=i,a[0]=a[0].slice(i)}return a}function i(){var e={value:null,prev:null,next:null},n={value:null,prev:e,next:null};e.next=n,this.head=e,this.tail=n,this.length=0}function I(e,n,t){var r=n.next,a={value:t,prev:n,next:r};return n.next=a,r.prev=a,e.length++,a}function q(e,n,t){for(var r=n.next,a=0;a<t&&r!==e.tail;a++)r=r.next;(n.next=r).prev=n,e.length-=a}if(u.Prism=M,W.stringify=function n(e,t){if("string"==typeof e)return e;if(Array.isArray(e)){var r="";return e.forEach(function(e){r+=n(e,t)}),r}var a={type:e.type,content:n(e.content,t),tag:"span",classes:["token",e.type],attributes:{},language:t},i=e.alias;i&&(Array.isArray(i)?Array.prototype.push.apply(a.classes,i):a.classes.push(i)),M.hooks.run("wrap",a);var l="";for(var o in a.attributes)l+=" "+o+'="'+(a.attributes[o]||"").replace(/"/g,""")+'"';return"<"+a.tag+' class="'+a.classes.join(" ")+'"'+l+">"+a.content+"</"+a.tag+">"},!u.document)return u.addEventListener&&(M.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(M.highlight(r,M.languages[t],t)),a&&u.close()},!1)),M;var t=M.util.currentScript();function r(){M.manual||M.highlightAll()}if(t&&(M.filename=t.src,t.hasAttribute("data-manual")&&(M.manual=!0)),!M.manual){var a=document.readyState;"loading"===a||"interactive"===a&&t&&t.defer?document.addEventListener("DOMContentLoaded",r):window.requestAnimationFrame?window.requestAnimationFrame(r):window.setTimeout(r,16)}return M}(_self);"undefined"!=typeof module&&module.exports&&(module.exports=Prism),"undefined"!=typeof global&&(global.Prism=Prism);
-Prism.languages.markup={comment:{pattern:/<!--(?:(?!<!--)[\s\S])*?-->/,greedy:!0},prolog:{pattern:/<\?[\s\S]+?\?>/,greedy:!0},doctype:{pattern:/<!DOCTYPE(?:[^>"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|<!--(?:[^-]|-(?!->))*-->)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(^[^\[]*\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^<!|>$|[[\]]/,"doctype-tag":/^DOCTYPE/i,name:/[^\s<>'"]+/}},cdata:{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,greedy:!0},tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"special-attr":[],"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/&#x?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^<!\[CDATA\[)[\s\S]+?(?=\]\]>$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^<!\[CDATA\[|\]\]>$/i;var t={"included-cdata":{pattern:/<!\[CDATA\[[\s\S]*?\]\]>/i,inside:s}};t["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var n={};n[a]={pattern:RegExp("(<__[^>]*>)(?:<!\\[CDATA\\[(?:[^\\]]|\\](?!\\]>))*\\]\\]>|(?!<!\\[CDATA\\[)[^])*?(?=</__>)".replace(/__/g,function(){return a}),"i"),lookbehind:!0,greedy:!0,inside:t},Prism.languages.insertBefore("markup","cdata",n)}}),Object.defineProperty(Prism.languages.markup.tag,"addAttribute",{value:function(a,e){Prism.languages.markup.tag.inside["special-attr"].push({pattern:RegExp("(^|[\"'\\s])(?:"+a+")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))","i"),lookbehind:!0,inside:{"attr-name":/^[^\s=]+/,"attr-value":{pattern:/=[\s\S]+/,inside:{value:{pattern:/(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/,lookbehind:!0,alias:[e,"language-"+e],inside:Prism.languages[e]},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}}}})}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml;
-!function(s){var e=/(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/;s.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-](?:[^;{\s]|\s+(?![\s{]))*(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+e.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+e.source+"$"),alias:"url"}}},selector:{pattern:RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|"+e.source+")*(?=\\s*\\{)"),lookbehind:!0},string:{pattern:e,greedy:!0},property:{pattern:/(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i,lookbehind:!0},important:/!important\b/i,function:{pattern:/(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i,lookbehind:!0},punctuation:/[(){};:,]/},s.languages.css.atrule.inside.rest=s.languages.css;var t=s.languages.markup;t&&(t.tag.addInlined("style","css"),t.tag.addAttribute("style","css"))}(Prism);
-Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0,greedy:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\b\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
-Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|\})\s*)catch\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],function:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*\]|\\.|[^/\\\[\r\n])+\/[dgimyus]{0,7}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-delimiter":/^\/|\/$/,"regex-flags":/^[a-z]+$/}},"function-variable":{pattern:/#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{hashbang:{pattern:/^#!.*/,greedy:!0,alias:"comment"},"template-string":{pattern:/`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\$\{|\}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&(Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)","javascript")),Prism.languages.js=Prism.languages.javascript;
-!function(e){var t="\\b(?:BASH|BASHOPTS|BASH_ALIASES|BASH_ARGC|BASH_ARGV|BASH_CMDS|BASH_COMPLETION_COMPAT_DIR|BASH_LINENO|BASH_REMATCH|BASH_SOURCE|BASH_VERSINFO|BASH_VERSION|COLORTERM|COLUMNS|COMP_WORDBREAKS|DBUS_SESSION_BUS_ADDRESS|DEFAULTS_PATH|DESKTOP_SESSION|DIRSTACK|DISPLAY|EUID|GDMSESSION|GDM_LANG|GNOME_KEYRING_CONTROL|GNOME_KEYRING_PID|GPG_AGENT_INFO|GROUPS|HISTCONTROL|HISTFILE|HISTFILESIZE|HISTSIZE|HOME|HOSTNAME|HOSTTYPE|IFS|INSTANCE|JOB|LANG|LANGUAGE|LC_ADDRESS|LC_ALL|LC_IDENTIFICATION|LC_MEASUREMENT|LC_MONETARY|LC_NAME|LC_NUMERIC|LC_PAPER|LC_TELEPHONE|LC_TIME|LESSCLOSE|LESSOPEN|LINES|LOGNAME|LS_COLORS|MACHTYPE|MAILCHECK|MANDATORY_PATH|NO_AT_BRIDGE|OLDPWD|OPTERR|OPTIND|ORBIT_SOCKETDIR|OSTYPE|PAPERSIZE|PATH|PIPESTATUS|PPID|PS1|PS2|PS3|PS4|PWD|RANDOM|REPLY|SECONDS|SELINUX_INIT|SESSION|SESSIONTYPE|SESSION_MANAGER|SHELL|SHELLOPTS|SHLVL|SSH_AUTH_SOCK|TERM|UID|UPSTART_EVENTS|UPSTART_INSTANCE|UPSTART_JOB|UPSTART_SESSION|USER|WINDOWID|XAUTHORITY|XDG_CONFIG_DIRS|XDG_CURRENT_DESKTOP|XDG_DATA_DIRS|XDG_GREETER_DATA_DIR|XDG_MENU_PREFIX|XDG_RUNTIME_DIR|XDG_SEAT|XDG_SEAT_PATH|XDG_SESSION_DESKTOP|XDG_SESSION_ID|XDG_SESSION_PATH|XDG_SESSION_TYPE|XDG_VTNR|XMODIFIERS)\\b",n={pattern:/(^(["']?)\w+\2)[ \t]+\S.*/,lookbehind:!0,alias:"punctuation",inside:null},a={bash:n,environment:{pattern:RegExp("\\$"+t),alias:"constant"},variable:[{pattern:/\$?\(\([\s\S]+?\)\)/,greedy:!0,inside:{variable:[{pattern:/(^\$\(\([\s\S]+)\)\)/,lookbehind:!0},/^\$\(\(/],number:/\b0x[\dA-Fa-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:[Ee]-?\d+)?/,operator:/--|\+\+|\*\*=?|<<=?|>>=?|&&|\|\||[=!+\-*/%<>^&|]=?|[?~:]/,punctuation:/\(\(?|\)\)?|,|;/}},{pattern:/\$\((?:\([^)]+\)|[^()])+\)|`[^`]+`/,greedy:!0,inside:{variable:/^\$\(|^`|\)$|`$/}},{pattern:/\$\{[^}]+\}/,greedy:!0,inside:{operator:/:[-=?+]?|[!\/]|##?|%%?|\^\^?|,,?/,punctuation:/[\[\]]/,environment:{pattern:RegExp("(\\{)"+t),lookbehind:!0,alias:"constant"}}},/\$(?:\w+|[#?*!@$])/],entity:/\\(?:[abceEfnrtv\\"]|O?[0-7]{1,3}|x[0-9a-fA-F]{1,2}|u[0-9a-fA-F]{4}|U[0-9a-fA-F]{8})/};e.languages.bash={shebang:{pattern:/^#!\s*\/.*/,alias:"important"},comment:{pattern:/(^|[^"{\\$])#.*/,lookbehind:!0},"function-name":[{pattern:/(\bfunction\s+)[\w-]+(?=(?:\s*\(?:\s*\))?\s*\{)/,lookbehind:!0,alias:"function"},{pattern:/\b[\w-]+(?=\s*\(\s*\)\s*\{)/,alias:"function"}],"for-or-select":{pattern:/(\b(?:for|select)\s+)\w+(?=\s+in\s)/,alias:"variable",lookbehind:!0},"assign-left":{pattern:/(^|[\s;|&]|[<>]\()\w+(?=\+?=)/,inside:{environment:{pattern:RegExp("(^|[\\s;|&]|[<>]\\()"+t),lookbehind:!0,alias:"constant"}},alias:"variable",lookbehind:!0},string:[{pattern:/((?:^|[^<])<<-?\s*)(\w+)\s[\s\S]*?(?:\r?\n|\r)\2/,lookbehind:!0,greedy:!0,inside:a},{pattern:/((?:^|[^<])<<-?\s*)(["'])(\w+)\2\s[\s\S]*?(?:\r?\n|\r)\3/,lookbehind:!0,greedy:!0,inside:{bash:n}},{pattern:/(^|[^\\](?:\\\\)*)"(?:\\[\s\S]|\$\([^)]+\)|\$(?!\()|`[^`]+`|[^"\\`$])*"/,lookbehind:!0,greedy:!0,inside:a},{pattern:/(^|[^$\\])'[^']*'/,lookbehind:!0,greedy:!0},{pattern:/\$'(?:[^'\\]|\\[\s\S])*'/,greedy:!0,inside:{entity:a.entity}}],environment:{pattern:RegExp("\\$?"+t),alias:"constant"},variable:a.variable,function:{pattern:/(^|[\s;|&]|[<>]\()(?:add|apropos|apt|aptitude|apt-cache|apt-get|aspell|automysqlbackup|awk|basename|bash|bc|bconsole|bg|bzip2|cal|cat|cfdisk|chgrp|chkconfig|chmod|chown|chroot|cksum|clear|cmp|column|comm|composer|cp|cron|crontab|csplit|curl|cut|date|dc|dd|ddrescue|debootstrap|df|diff|diff3|dig|dir|dircolors|dirname|dirs|dmesg|du|egrep|eject|env|ethtool|expand|expect|expr|fdformat|fdisk|fg|fgrep|file|find|fmt|fold|format|free|fsck|ftp|fuser|gawk|git|gparted|grep|groupadd|groupdel|groupmod|groups|grub-mkconfig|gzip|halt|head|hg|history|host|hostname|htop|iconv|id|ifconfig|ifdown|ifup|import|install|ip|jobs|join|kill|killall|less|link|ln|locate|logname|logrotate|look|lpc|lpr|lprint|lprintd|lprintq|lprm|ls|lsof|lynx|make|man|mc|mdadm|mkconfig|mkdir|mke2fs|mkfifo|mkfs|mkisofs|mknod|mkswap|mmv|more|most|mount|mtools|mtr|mutt|mv|nano|nc|netstat|nice|nl|nohup|notify-send|npm|nslookup|op|open|parted|passwd|paste|pathchk|ping|pkill|pnpm|popd|pr|printcap|printenv|ps|pushd|pv|quota|quotacheck|quotactl|ram|rar|rcp|reboot|remsync|rename|renice|rev|rm|rmdir|rpm|rsync|scp|screen|sdiff|sed|sendmail|seq|service|sftp|sh|shellcheck|shuf|shutdown|sleep|slocate|sort|split|ssh|stat|strace|su|sudo|sum|suspend|swapon|sync|tac|tail|tar|tee|time|timeout|top|touch|tr|traceroute|tsort|tty|umount|uname|unexpand|uniq|units|unrar|unshar|unzip|update-grub|uptime|useradd|userdel|usermod|users|uudecode|uuencode|v|vdir|vi|vim|virsh|vmstat|wait|watch|wc|wget|whereis|which|who|whoami|write|xargs|xdg-open|yarn|yes|zenity|zip|zsh|zypper)(?=$|[)\s;|&])/,lookbehind:!0},keyword:{pattern:/(^|[\s;|&]|[<>]\()(?:if|then|else|elif|fi|for|while|in|case|esac|function|select|do|done|until)(?=$|[)\s;|&])/,lookbehind:!0},builtin:{pattern:/(^|[\s;|&]|[<>]\()(?:\.|:|break|cd|continue|eval|exec|exit|export|getopts|hash|pwd|readonly|return|shift|test|times|trap|umask|unset|alias|bind|builtin|caller|command|declare|echo|enable|help|let|local|logout|mapfile|printf|read|readarray|source|type|typeset|ulimit|unalias|set|shopt)(?=$|[)\s;|&])/,lookbehind:!0,alias:"class-name"},boolean:{pattern:/(^|[\s;|&]|[<>]\()(?:true|false)(?=$|[)\s;|&])/,lookbehind:!0},"file-descriptor":{pattern:/\B&\d\b/,alias:"important"},operator:{pattern:/\d?<>|>\||\+=|=[=~]?|!=?|<<[<-]?|[&\d]?>>|\d[<>]&?|[<>][&=]?|&[>&]?|\|[&|]?/,inside:{"file-descriptor":{pattern:/^\d/,alias:"important"}}},punctuation:/\$?\(\(?|\)\)?|\.\.|[{}[\];\\]/,number:{pattern:/(^|\s)(?:[1-9]\d*|0)(?:[.,]\d+)?\b/,lookbehind:!0}},n.inside=e.languages.bash;for(var s=["comment","function-name","for-or-select","assign-left","string","environment","function","keyword","builtin","boolean","file-descriptor","operator","punctuation","number"],i=a.variable[1].inside,o=0;o<s.length;o++)i[s[o]]=e.languages.bash[s[o]];e.languages.shell=e.languages.bash}(Prism);
-Prism.languages.git={comment:/^#.*/m,deleted:/^[-–].*/m,inserted:/^\+.*/m,string:/("|')(?:\\.|(?!\1)[^\\\r\n])*\1/m,command:{pattern:/^.*\$ git .*$/m,inside:{parameter:/\s--?\w+/m}},coord:/^@@.*@@$/m,"commit-sha1":/^commit \w{40}$/m};
-Prism.languages.python={comment:{pattern:/(^|[^\\])#.*/,lookbehind:!0},"string-interpolation":{pattern:/(?:f|rf|fr)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i,greedy:!0,inside:{interpolation:{pattern:/((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/,lookbehind:!0,inside:{"format-spec":{pattern:/(:)[^:(){}]+(?=\}$)/,lookbehind:!0},"conversion-option":{pattern:/![sra](?=[:}]$)/,alias:"punctuation"},rest:null}},string:/[\s\S]+/}},"triple-quoted-string":{pattern:/(?:[rub]|rb|br)?("""|''')[\s\S]*?\1/i,greedy:!0,alias:"string"},string:{pattern:/(?:[rub]|rb|br)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i,greedy:!0},function:{pattern:/((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g,lookbehind:!0},"class-name":{pattern:/(\bclass\s+)\w+/i,lookbehind:!0},decorator:{pattern:/(^[\t ]*)@\w+(?:\.\w+)*/im,lookbehind:!0,alias:["annotation","punctuation"],inside:{punctuation:/\./}},keyword:/\b(?:and|as|assert|async|await|break|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/,builtin:/\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/,boolean:/\b(?:True|False|None)\b/,number:/\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?\b/i,operator:/[-+%=]=?|!=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/,punctuation:/[{}[\];(),.:]/},Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest=Prism.languages.python,Prism.languages.py=Prism.languages.python;
-Prism.languages.r={comment:/#.*/,string:{pattern:/(['"])(?:\\.|(?!\1)[^\\\r\n])*\1/,greedy:!0},"percent-operator":{pattern:/%[^%\s]*%/,alias:"operator"},boolean:/\b(?:TRUE|FALSE)\b/,ellipsis:/\.\.(?:\.|\d+)/,number:[/\b(?:NaN|Inf)\b/,/(?:\b0x[\dA-Fa-f]+(?:\.\d*)?|\b\d+(?:\.\d*)?|\B\.\d+)(?:[EePp][+-]?\d+)?[iL]?/],keyword:/\b(?:if|else|repeat|while|function|for|in|next|break|NULL|NA|NA_integer_|NA_real_|NA_complex_|NA_character_)\b/,operator:/->?>?|<(?:=|<?-)?|[>=!]=?|::?|&&?|\|\|?|[+*\/^$@~]/,punctuation:/[(){}\[\],;]/};
-!function(e){function n(e){return e.replace(/__/g,function(){return"(?:[\\w-]+|'[^'\n\r]*'|\"(?:\\\\.|[^\\\\\"\r\n])*\")"})}e.languages.toml={comment:{pattern:/#.*/,greedy:!0},table:{pattern:RegExp(n("(^[\t ]*\\[\\s*(?:\\[\\s*)?)__(?:\\s*\\.\\s*__)*(?=\\s*\\])"),"m"),lookbehind:!0,greedy:!0,alias:"class-name"},key:{pattern:RegExp(n("(^[\t ]*|[{,]\\s*)__(?:\\s*\\.\\s*__)*(?=\\s*=)"),"m"),lookbehind:!0,greedy:!0,alias:"property"},string:{pattern:/"""(?:\\[\s\S]|[^\\])*?"""|'''[\s\S]*?'''|'[^'\n\r]*'|"(?:\\.|[^\\"\r\n])*"/,greedy:!0},date:[{pattern:/\b\d{4}-\d{2}-\d{2}(?:[T\s]\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})?)?\b/i,alias:"number"},{pattern:/\b\d{2}:\d{2}:\d{2}(?:\.\d+)?\b/,alias:"number"}],number:/(?:\b0(?:x[\da-zA-Z]+(?:_[\da-zA-Z]+)*|o[0-7]+(?:_[0-7]+)*|b[10]+(?:_[10]+)*))\b|[-+]?\b\d+(?:_\d+)*(?:\.\d+(?:_\d+)*)?(?:[eE][+-]?\d+(?:_\d+)*)?\b|[-+]?\b(?:inf|nan)\b/,boolean:/\b(?:true|false)\b/,punctuation:/[.,=[\]{}]/}}(Prism);
-!function(){if("undefined"!=typeof Prism&&"undefined"!=typeof document){var i=[],l={},d=function(){};Prism.plugins.toolbar={};var e=Prism.plugins.toolbar.registerButton=function(e,n){var t;t="function"==typeof n?n:function(e){var t;return"function"==typeof n.onClick?((t=document.createElement("button")).type="button",t.addEventListener("click",function(){n.onClick.call(this,e)})):"string"==typeof n.url?(t=document.createElement("a")).href=n.url:t=document.createElement("span"),n.className&&t.classList.add(n.className),t.textContent=n.text,t},e in l?console.warn('There is a button with the key "'+e+'" registered already.'):i.push(l[e]=t)},t=Prism.plugins.toolbar.hook=function(a){var e=a.element.parentNode;if(e&&/pre/i.test(e.nodeName)&&!e.parentNode.classList.contains("code-toolbar")){var t=document.createElement("div");t.classList.add("code-toolbar"),e.parentNode.insertBefore(t,e),t.appendChild(e);var r=document.createElement("div");r.classList.add("toolbar");var n=i,o=function(e){for(;e;){var t=e.getAttribute("data-toolbar-order");if(null!=t)return(t=t.trim()).length?t.split(/\s*,\s*/g):[];e=e.parentElement}}(a.element);o&&(n=o.map(function(e){return l[e]||d})),n.forEach(function(e){var t=e(a);if(t){var n=document.createElement("div");n.classList.add("toolbar-item"),n.appendChild(t),r.appendChild(n)}}),t.appendChild(r)}};e("label",function(e){var t=e.element.parentNode;if(t&&/pre/i.test(t.nodeName)&&t.hasAttribute("data-label")){var n,a,r=t.getAttribute("data-label");try{a=document.querySelector("template#"+r)}catch(e){}return a?n=a.content:(t.hasAttribute("data-url")?(n=document.createElement("a")).href=t.getAttribute("data-url"):n=document.createElement("span"),n.textContent=r),n}}),Prism.hooks.add("complete",t)}}();
-!function(){function u(t,e){t.addEventListener("click",function(){!function(t){navigator.clipboard?navigator.clipboard.writeText(t.getText()).then(t.success,function(){o(t)}):o(t)}(e)})}function o(e){var t=document.createElement("textarea");t.value=e.getText(),t.style.top="0",t.style.left="0",t.style.position="fixed",document.body.appendChild(t),t.focus(),t.select();try{var o=document.execCommand("copy");setTimeout(function(){o?e.success():e.error()},1)}catch(t){setTimeout(function(){e.error(t)},1)}document.body.removeChild(t)}"undefined"!=typeof Prism&&"undefined"!=typeof document&&(Prism.plugins.toolbar?Prism.plugins.toolbar.registerButton("copy-to-clipboard",function(t){var e=t.element,o=function(t){var e={copy:"Copy","copy-error":"Press Ctrl+C to copy","copy-success":"Copied!","copy-timeout":5e3};for(var o in e){for(var n="data-prismjs-"+o,c=t;c&&!c.hasAttribute(n);)c=c.parentElement;c&&(e[o]=c.getAttribute(n))}return e}(e),n=document.createElement("button");n.className="copy-to-clipboard-button",n.setAttribute("type","button");var c=document.createElement("span");return n.appendChild(c),i("copy"),u(n,{getText:function(){return e.textContent},success:function(){i("copy-success"),r()},error:function(){i("copy-error"),setTimeout(function(){!function(t){window.getSelection().selectAllChildren(t)}(e)},1),r()}}),n;function r(){setTimeout(function(){i("copy")},o["copy-timeout"])}function i(t){c.textContent=o[t],n.setAttribute("data-copy-state",t)}}):console.warn("Copy to Clipboard plugin loaded before Toolbar plugin."))}();
diff --git a/js/tab-title.js b/js/tab-title.js
@@ -1,5 +0,0 @@
-var url = window.location.pathname; // gets the pathname of the file
-var str = url.substring(url.lastIndexOf('/')+1); // removes everything before the filename
-str = str.substring(0, str.length - 5); // removes the extension
-var filename = str.replace(/%20/g, " "); // if the filename has multiple words separated by spaces, browsers do not like that and replace each space with a %20. This replace %20 with a space.
-document.getElementById("title").innerHTML = filename;
diff --git a/plugins/atom.lua b/plugins/atom.lua
@@ -0,0 +1,89 @@
+-- Atom feed generator
+-- Still somewhat experimental -- do not steal for your site just yet
+
+Plugin.require_version("2.2.0")
+
+data = config
+
+date_input_formats = soupault_config["index"]["date_formats"]
+
+feed_file = config["feed_file"]
+
+data["site_url"] = soupault_config["custom_options"]["site_url"]
+data["feed_id"] = Sys.join_path(soupault_config["custom_options"]["site_url"], feed_file)
+
+data["soupault_version"] = Plugin.soupault_version()
+
+
+function in_section(entry)
+ return (entry["nav_path"][1] == config["use_section"])
+end
+
+entries = {}
+
+-- Original, unfiltered entries inded
+local n = 1
+
+-- Index of the new array of entries we are building
+local m = 1
+
+local count = size(site_index)
+while (n <= count) do
+ entry = site_index[n]
+ if in_section(entry) then
+ if entry["date"] then
+ entry["date"] = Date.reformat(entry["date"], date_input_formats, "%Y-%m-%dT%H:%M:%S%:z")
+ end
+ entries[m] = entry
+ m = m + 1
+
+ -- Remove unwanted elements (e.g. footnotes) from the excerpt
+ local excerpt = HTML.parse(entry["excerpt"])
+ Table.iter_values(HTML.delete, HTML.select_all_of(excerpt, config["delete_elements"]))
+ entry["excerpt"] = tostring(excerpt)
+ end
+ n = n + 1
+end
+
+if (soupault_config["index"]["sort_descending"] or
+ (not Table.has_key(soupault_config["index"], "sort_descending")))
+then
+ data["feed_last_updated"] = entries[1]["date"]
+else
+ data["feed_last_updated"] = entries[size(entries)]["date"]
+end
+
+data["entries"] = entries
+
+feed_template = [[
+{%- autoescape false -%}
+<?xml version='1.0' encoding='UTF-8'?>
+<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
+ <id>{{feed_id}}</id>
+ <title>{{feed_title}}</title>
+ <updated>{{feed_last_updated}}</updated>
+ <author>
+ <name>{{feed_author}}</name>
+ <email>{{feed_author_email}}</email>
+ </author>
+ <generator uri="https://soupault.app" version="{{soupault_version}}">soupault</generator>
+ <logo>{{feed_logo}}</logo>
+ <subtitle>{{feed_subtitle}}</subtitle>
+ {%- for e in entries %}
+ <entry>
+ <id>{{site_url}}{{e.url}}</id>
+ <title>{{e.title}}</title>
+ <updated>{{e.date}}</updated>
+ <content type="html">
+ {{e.excerpt}}
+ </content>
+ <link href="{{site_url}}{{e.url}}" rel="alternate"/>
+ </entry>
+ {% endfor %}
+</feed>
+{% endautoescape -%}
+]]
+
+feed = String.render_template(feed_template, data)
+
+Sys.write_file(Sys.join_path(soupault_config["settings"]["build_dir"], feed_file), String.trim(feed))
diff --git a/plugins/escape-html.lua b/plugins/escape-html.lua
@@ -0,0 +1,38 @@
+-- Escapes HTML special characters (<, >, &) in the content of elements
+-- matching a selector
+--
+-- Sample configuration that converts content of <pre> elements to its HTML source:
+-- [plugins.escape_html]
+-- file = "plugins/escape-html.lua"
+--
+-- [widgets.raw-html-in-pre]
+-- widget = "escape_html"
+-- selector = "pre"
+--
+-- Minimum soupault version: 1.6
+-- Author: Daniil Baturin
+-- License: MIT
+
+selector = config["selector"]
+if not selector then
+ Plugin.fail("Missing required option \"selector\"")
+end
+
+function escape_html(element)
+ content = HTML.inner_html(element)
+ -- HTML.create_text escapes HTML special characters
+ content = HTML.create_text(content)
+ HTML.replace_content(element, content)
+end
+
+elements = HTML.select(page, selector)
+
+if not elements then
+ Plugin.exit("No elements found, nothing to do")
+end
+
+local index = 1
+while elements[index] do
+ escape_html(elements[index])
+ index = index + 1
+end
diff --git a/plugins/reading-time.lua b/plugins/reading-time.lua
@@ -0,0 +1,62 @@
+-- Makes a reading time estimate based on word count.
+--
+-- Sample configuration:
+--
+-- [plugins.reading_time]
+-- file = "plugins/reading-time.lua"
+--
+-- [widgets.reading-time]
+-- widget = "reading_time"
+-- reading_speed = 350
+--
+-- # Where to insert the reading time estimate
+-- selector = "span#reading-time"
+--
+-- # Where to extract the text for word count
+-- content_selector = "main"
+--
+-- Minimum soupault version: 1.6
+-- Author: Daniil Baturin
+-- License: MIT
+
+reading_speed = config["reading_speed"]
+selector = config["selector"]
+content_selector = config["content_selector"]
+
+if (not reading_speed) then
+ Log.warning("Missing option \"reading_speed\", using default (300 WPM)")
+ reading_speed = 300
+end
+
+if (not selector) then
+ Log.warning("Missing option \"selector\", using default (body)")
+ selector = "body"
+end
+
+if (not content_selector) then
+ Log.warning("Missing option \"content_selector\", using default (body)")
+ content_selector = "body"
+end
+
+-- Extract the content
+content_element = HTML.select_one(page, content_selector)
+content = HTML.strip_tags(content_element)
+
+-- Calculate the word count
+words = Regex.split(content, "\\s+")
+word_count = size(words)
+
+-- Make a reading time text
+reading_time = floor(word_count / reading_speed)
+
+if (reading_time <= 1) then
+ time_msg = "less than a minute"
+else
+ time_msg = reading_time .. " minutes"
+end
+
+-- Insert the text in the target element
+target = HTML.select_one(page, selector)
+if target then
+ HTML.prepend_child(target, HTML.create_text(time_msg))
+end
diff --git a/plugins/section-link-highlight.lua b/plugins/section-link-highlight.lua
@@ -0,0 +1,65 @@
+-- Highlights the link to the current page/section in the navigation menu
+-- If you have <a href="/about">, it will add a CSS class to it on page site/about.html
+-- It assumes you are using relative links
+--
+-- Sample configuration:
+-- [plugins.active-link-hightlight]
+-- active_link_class = "active"
+-- nav_menu_selector = "nav"
+--
+-- Minimum soupault version: 1.6
+-- Author: Daniil Baturin
+-- License: MIT
+
+active_link_class = config["active_link_class"]
+nav_menu_selector = config["selector"]
+
+if (not active_link_class) then
+ Log.warning("active_link_class option is not set, using default (\"active\")")
+ Plugin.fail()
+ active_link_class = "active"
+end
+
+if (not nav_menu_selector) then
+ Log.warning("nav_menu_selector option is not set, using default (\"nav\")")
+ nav_menu_selector = "nav"
+end
+
+menu = HTML.select_one(page, nav_menu_selector)
+if (not menu) then
+ Plugin.exit("No element matched selector " .. nav_menu_selector .. ", nothing to do")
+end
+
+
+links = HTML.select(menu, "a")
+
+local index = 1
+while links[index] do
+ link = links[index]
+
+ href = HTML.get_attribute(link, "href")
+
+ if not href then
+ -- Link has no href attribute, ignore
+ else
+ href = strlower(href)
+
+ -- Remove leading and trailing slashes
+ href = Regex.replace_all(href, "(\\/?$|^\\/)", "")
+ page_url = Regex.replace_all(page_url, "(\\/?$|^\\/)", "")
+
+ -- Normalize slashes
+ href = Regex.replace_all(href, "\\/+", "\\/")
+
+ -- Edge case: the / link that becomes "" after normalization
+ -- Anything would match the empty string and higlight all links,
+ -- so we handle this case explicitly
+ if ((page_url == "") and (href == ""))
+ or ((href ~= "") and Regex.match(page_url, "^" .. href))
+ then
+ HTML.add_class(link, active_link_class)
+ end
+ end
+
+ index = index + 1
+end
diff --git a/plugins/set-tab-index.lua b/plugins/set-tab-index.lua
@@ -0,0 +1,28 @@
+selectors = config["selectors"]
+tab_index = config["tab_index"]
+
+if not Value.is_list(selectors) then
+ Plugin.fail("selectors option must be a list")
+end
+
+if not Value.is_int(tab_index) then
+ Plugin.fail("tab_index option must be an integer")
+end
+
+-- Set the tabindex attribute for a single element
+function set_elem_tab_index(elem, index)
+ current_index = HTML.get_attribute(elem, "tabindex")
+
+ -- Only set tabindex for elements where it's not set already
+ if current_index == nil then
+ HTML.set_attribute(elem, "tabindex", tab_index)
+ end
+end
+
+-- Sets the tabindex attribute for all elements that match a selector
+function set_tab_index(selector)
+ elems = HTML.select(page, selector)
+ Table.iter_values(set_elem_tab_index, elems)
+end
+
+Table.iter_values(set_tab_index, selectors)
diff --git a/posts.md b/posts.md
@@ -1,14 +0,0 @@
-# Posts
-
-## [fix MIME Types to unbreak RSS feeds served by OpenBSD's httpd(8)](posts/mime.html)
-
-## [upgrade old OpenBSD installs](posts/ugbsd.html)
-
-## [geocheatcode](posts/geocheatcode.html)
-
-## [set up enterprise wifi on arch linux](posts/employee-wifi.html)
-
-## [morning report 08/2021](posts/mr-2021.html)
-
-
-See old posts [here](https://cbeauhilton.github.io)
diff --git a/posts/employee-wifi.md b/posts/employee-wifi.md
@@ -1,207 +0,0 @@
-# Set Up Enterprise Wifi on Arch Linux
-
-This was a little tricky to get working
-but very worth it,
-so here's an outline,
-mostly for my own later benefit.
-
-This post is specific to [VUMC](https://www.vumc.org),
-with the VUMCEmployee network.
-
-Similar steps should be applicable for other enterprise wifi users,
-though this post will unquestionably be out of date before long,
-and the intricacies of enterprise wifi are infinite.
-
-## VUMCGuest is fine
-
-As with other public networks at large institutions,
-VUMCGuest is just a little slow and finicky,
-and it's annoying to have to re-authenticate repeatedly
-to use all the HIPAA-compliant things.
-
-## VUMCEmployee is better
-
-I'll probably put a screenshot here at some point
-comparing speedtest scores.
-VUMCEmployee gives
-over 100 Mbps down,
-and around 100 up.
-
-It's also more stable,
-and latency is around 10ms.
-
-Most practical gain,
-other than faster everything:
-When I use VUMCGuest,
-the keyboard shortcut I use to
-launch and automatically login to Epic
-only works intermittently.
-On VUMCEmployee, it works reliably.
-No more typing!
-It's faster and, again, more reliable
-than tapping the badge-readers at the VUMC workstations.
-
-## Backend
-
-The personal networking stack
-of greatest beauty
-on Linux
-at this point is:
-
-`systemd-networkd` +`systemd-resolved` + `iwd`
-
-Disable and delete `NetworkManager`
-and other such nonsense,
-if you are unwise like me
-and installed conflicting and useless things.
-
-If you'd like a GUI, [iwgtk](https://github.com/J-Lentz/iwgtk) is nice,
-but the CLI shipped with `iwd` (`iwctl`)
-is intuitive, friendly, and well-documented.
-I keep the GUI version around for quickly checking on things
-via a keyboard shortcut,
-but use the CLI for any heavy lifting,
-which has thankfully become rare since landing on this setup.
-
-## Start with VUMCEmployeeSetup
-
-First, log on to the VUMCEmployeeSetup wifi.
-Then navigate to one of my favorite websites, <http://neverssl.com/>.
-This will force the redirect to the VUMCEmployee enrollment page
-(I also use this site for connecting to public wifi
-at airports, libraries, coffee shops, etc.).
-Agree to the terms and conditions.
-Then click the "Show all operating systems" link at the bottom,
-followed by the "Other Operating Systems" tab
-that pops up at the bottom of the list.
-
-The "Other Operating Systems" tab has
-three steps listed,
-which are simply the pieces that the
-various installers put together for you.
-The first two are downloads for certificates,
-and the third is a template.
-
-Finding this tab
-was the gold mine - initially I
-repackaged one of the other Linux installers for Arch,
-because I thought that (since there was an installer)
-the process must be complicated,
-and repackaging things from Debian-based systems
-for Arch-based systems is easy enough.
-The repackaged version of the installer
-was decent at first,
-but it turns out that
-the manual process is easier and more reliable.
-I also learned more about enterprise networks in the process,
-which was an added bonus
-(I'm honestly not sure about the
-sarcasm:sincerity ratio in the previous sentence).
-
-Download the `PEM` files listed under
-Steps 1 (root certificate) and
-2 (client certificate).
-
-## Make your own `iwd` profile
-
-Here's where it goes:
-`/var/lib/iwd/VUMCEmployee.8021x`
-
-Below are the contents,
-sensitive info redacted,
-then we'll go through some of the key parts
-and one nicety.
-
-```toml
-[IPv6]
-Enabled=true
-
-[Security]
-EAP-Method=PEAP
-EAP-Identity=username
-EAP-PEAP-CACert=embed:root_cert
-EAP-PEAP-ServerDomainMask=*.radius.service.vumc.org
-EAP-PEAP-Phase2-Method=MSCHAPV2
-EAP-PEAP-Phase2-Identity=username
-EAP-PEAP-Phase2-Password=password
-
-[Settings]
-AutoConnect=true
-
-[@pem@root_cert]
------BEGIN CERTIFICATE-----
-*lots of gobbledigook goes here*
------END CERTIFICATE-----
-```
-
-Most of these options are outlined in
-Step 3 from the VUMCEmployeeSetup,
-cross-referenced against the Arch Wiki page on `iwd`,
-subsection [Network configuration](https://wiki.archlinux.org/title/Iwd#EAP-PEAP),
-and the [`iwd` wiki proper](https://iwd.wiki.kernel.org/networkconfigurationsettings).
-
-An easy-to-miss step:
-The `EAP-PEAP-Phase2-Method` requirement for `MSCHAPV2`
-leads to another required install,
-check the wiki for current instructions.
-
-Put in your own username and password.
-
-My favorite trick in this file is
-directly embedding the root certificate
-in the line
-`EAP-PEAP-CACert=`
-with the syntax
-`embed:root_cert`
-(any name is fine,
-doesn't have to be `root_cert`,
-it's just a pointer).
-Then you add a definition of `root_cert` in a
-`[@pem@root_cert]` section.
-Insert the contents of the root certificate directly
-via copy-paste or `cat`, etc.
-
-Easiest method, as root:
-
-```shell
-cat /home/beau/dl/root_cert.PEM >> /var/lib/iwd/VUMCEmployee.8021x
-```
-
-With the direct embed method,
-you don't need to point to the root certificate file
-or keep it around at all.
-
-Needless to say,
-`VUMCEmployee.8021x`
-is a sensitive file and should be protected appropriately.
-However, this file or a version of it
-is what the automated tools would have made anyway,
-so there's no special risk here -
-AND since you did it all yourself
-you know there was no funny business
-coming from a black-box installer.
-
-## The other certificate (Client)
-
-I can't remember what I had to do with the client cert,
-probably added using the Chrome/Firefox certificate
-managers.
-
-I had to do this before when getting set up for VA remote access,
-the Arch Wiki comes through again with an article on
-[Common Access Cards](https://wiki.archlinux.org/title/Common_Access_Card)
-that includes instructions on adding certs to browsers.
-
-There's a chance it's not even needed?
-The [specification](https://iwd.wiki.kernel.org/networkconfigurationsettings)
-no longer supports
-adding a client cert field
-without a key,
-which I don't have,
-and do not, apparently, need
-(see the section "EAP-PEAP with tunneled EAP-MSCHAPV2").
-At any rate, this setup is working now
-and I won't futz with it further
-until something breaks.
-
-## -> ~~Profit~~ Prosper
diff --git a/posts/geocheatcode.md b/posts/geocheatcode.md
@@ -1,193 +0,0 @@
-
-# geocheatcode
-
-Here is background and code
-for a trick I use to get
-Google to give me best-in-class guesses
-for latitude and longitude,
-despite goofy and/or downright bad location searches.
-
-## Map all the things
-
-I love maps.
-
-Several of my projects involve mapping things at scale.
-
-When you want to map a few things,
-you type searches into Google Maps
-and get addresses and/or latitudes and longitudes
-quickly and reliably.
-
-But what if you'd like to map 90,000 things
-whose locations you don't yet know?
-
-[Google](https://developers.google.com/maps)
-and
-[OpenStreetMap](https://www.openstreetmap.org/),
-as well as others,
-provide mapping services
-you can call programmatically from your software.
-You send in some query,
-such as "VUMC Internal Medicine,"
-and they return information
-relevant to that query,
-such as street address and
-latitude and longitude.
-Up to a certain number of queries per day or hour,
-the services are free,
-and since my work is academic,
-rather than real-time mapping for some
-for-profit app,
-I am happy to send in small batches
-to stay under the limits in the free tier.
-
-I've used these services to make large maps,
-and they work pretty well.
-
-*Pretty* well.
-
-## But mapping is hard
-
-Problems with these services:
-
-1. they expected well-formed and reasonable queries
-2. if they didn't know the answer, the guesses were often wildly off, or they would refuse to guess at all
-
-If I'm mapping 90,000 things,
-I'm going to write some code
-to go through each of those 90,000 things
-and ask the mapping services
-to kindly tell me what I want to know.
-Though I write sanitation code to clean up the 90,000 things,
-I'm not going to quality check each of those 90,000 things.
-Sometimes things among the 90,000 things are kinda nuts
-(misspelled, inclusive of extraneous data, oddly formatted),
-in idiosyncratic ways that are impossible to completely cover,
-no matter how much code I write to catch the weird cases.
-
-I would like a solution that is fairly tolerant of weirdnesses,
-and makes good guesses.
-
-## Google is really good at search
-
-I noticed that when I manually typed things
-into the Google Maps search bar,
-it forgave a myriad of sins
-and did a great job centering the map on its best guess.
-When I copied and pasted some of the weird things among the 90,000
-into the Google Maps search bar
-(the same things that made the
-official mapping services - including Google's -
-go all Poltergeist),
-*voila!*, the right answer appeared,
-success rates nearing 100%.
-
-I thought there must be a way to repeat this process with code,
-in a scalable way.
-
-Turns out there is, and it's easy.
-
-## `geocheatcode.py`
-
-```python
-
-from requests_html import HTMLSession
-
-session = HTMLSession()
-
-
-def google_lat_lon(query: str):
-
- url = "https://www.google.com/maps/search/?api=1"
- params = {}
- params["query"] = query
-
- r = session.get(url, params=params)
-
- reg = "APP_INITIALIZATION_STATE=[[[{}]"
- res = r.html.search(reg)[0]
- lat = res.split(",")[2]
- lon = res.split(",")[1]
-
- return lat, lon
-
-
-extraneous = """ something something
- the earth is banana shaped
- latitude and longitude
- wouldn't you like to know, maybe """
-
-relevant = """ Vanderbilt University Medical Center
- Internal Medicine """
-
-query = extraneous + relevant
-
-lat, lon = google_lat_lon(query)
-
-print(
- "Hello. "
- "My name is Google. "
- "I am really good at guessing what you meant. "
- f"Your query was '{query}'. "
- "Here are the coordinates you probably wanted. "
- f"The latitude is {lat}, and the longitude is {lon}. "
- "Don't believe me? "
- "Here it is again, "
- "in a format you can paste into the search bar: \n"
- f"{lat}, {lon} \n"
- "Told ya. "
-)
-
-```
-
-Despite having all that extra junk in the query,
-this returns the right answer.
-Because Google is many things good and evil,
-but of these one is certain:
-Google is *really* good at search.
-
-## How does the code work?
-
-If you inspect the source HTML
-on the Google Maps website
-after you search for something
-and it centers the map on its best guess,
-and you scroll way on down (or Ctrl-F search for it)
-you'll find `APP_INITIALIZATION_STATE`, which contains
-latitude and longitude for the place the map centered on.
-
-- [example search](https://www.google.com/maps?q=something+whose+latitude+and+longitude+you+would+like+to+know,+maybe+VUMC+Internal+Medicine)
-- [example source](view-source:https://www.google.com/maps/search/something+whose+latitude+and+longitude+you+would+like+to+know,+maybe+VUMC+Internal+Medicine/) (you have to copy and paste this link into a new tab manually, clicking won't work)
-
-I use the lovely
-[`requests-html`](https://docs.python-requests.org/projects/requests-html/en/latest/)
-Python library
-to send the query to Google,
-receive the response,
-and search through the response for the part I want to extract.
-Then I use a little standard Python
-to parse the extracted part and save the important bits.
-
-## With great power...
-
-Don't go crazy with this.
-
-The trick is good for
-leisurely automation
-of location retrieval
-when you have squirrelly queries.
-
-If you need real-time mapping of many things,
-you don't want this solution.
-Use the actual APIs,
-and work instead on formatting the queries properly
-before sending them to Google/OSM.
-
-Also, if you try to query too much/too quickly,
-Google will shut you out after a little while.
-Put a few seconds of delay between each request
-and run it overnight and/or in automated batches.
-
-## Know a better way?
-
-I'd love to know. Drop me a line.
diff --git a/posts/intake-2022-03-25.md b/posts/intake-2022-03-25.md
@@ -1,44 +0,0 @@
-# intake
-
-## cc: trouble swallowing and weight loss
-
-28M w few weeks of trouble swallowing (gets stuck "right here," points to mid-sternum),
-solid/liquids same,
-gradual over months-weeks,
-some vomiting w/o specific timing.
-Sometimes has pain when not eating.
-20lb weight loss over months.
-No skin lesions.
-?thrush.
-
-## PMHx/PSHx
-
-dx BPD, no other dx or procedures
-
-
-## SHx
-
-- MSM w occasional use of protection, no PrEP
-- occ MJ use, no other substances
-- unemployed, lives w mom
-- no unusual hobbies or travel
-
-## PE
-
-HR 100, SBP 80 -> 100 w 500mL LR, AF
-cachectic (temporal, hypothenar wasting)
-+skin tenting
-diffuse abd tenderness
-
-## w/u
-
-Hgb 10, MCV 88, WBC ~4, ANC 1500
-BMP wnl
-Alb 3.4
-HIV+, VL 15k, rest of STI -ve
-
-CXR wnl (AP and lateral)
-
-## dx
-
-candidal esophagitis, achalasia, H Pylori PUD
diff --git a/posts/mime.md b/posts/mime.md
@@ -1,95 +0,0 @@
-# fix MIME Types to unbreak RSS feeds served by OpenBSD's httpd(8)
-
-
-## RSS is life - but mine was broken
-
-
-As of today (2022-11-13) my website lives on
-an OpenBSD server hosted at [vultr](https://vultr.com).
-
-It's great, delightfully simple and low-resource,
-robust, extendable, low-maintenance.
-
-I've been getting back into RSS lately.
-Turns out, my own RSS feed was broken.
-I knew it was janky, but would have had no idea how broken it was
-if not for the great folks
-on the [datasette](https://datasette.io) Discord,
-one of whom reached out to let me know my RSS link wasn't working.
-
-This could not stand!
-
-I've been meaning to fix my RSS feed anyway,
-and now I had a good reason.
-
-
-## fixing the file itself
-
-
-I ended up tearing out my previous RSS solution, [`rssg`](https://romanzolotarev.com/rssg.html),which is great but made some assumptions about my site's layout that aren't true.
-I could have rewritten the script,
-but I'm lazy and a little strapped for time,
-so I ended up replacing it with a hand-written RSS file.
-
-(The RSS spec is easy enough to write by hand,
-a little copy-paste and replace to add a new article -
-at some point I'll probably migrate to `hugo` or similar
-and hand off the feed creation to a more flexible script,
-but for now this works).
-
-After I was certain the file format was fine and had the info I wanted,
-I thought I was good.
-
-
-## fixing the MIME Type
-
-
-The kind soul who reached out to let me know the RSS feed was malformed
-reached out again to let me know he was now getting a MIME Type error.
-
-My feedreader of choice, `newsboat`,
-is very forgiving of what it accepts,
-and didn't throw any errors when I tested it.
-`FreshRSS`, on the other hand, is more strict,
-and the feed would fail even though the file itself was fine.
-
-I looked into it, and found out that `httpd(8)`
-only supports a handful of MIME Types by default,
-so my server was sending out `application/octet-stream`
-(a generic type) instead of the `rss+xml` type,
-and it was confusing the feedreader.
-
-
-## add all the types
-
-
-Thank goodness, and as usual in OpenBSD,
-there's a very easy way to add all the relevant types one might need.
-
-OpenBSD has an internal MIME declaration file you can link to from within `httpd.conf(5)`.
-
-Here's the relevant bit, just chuck this on the end of the conf file:
-
-```shell
-
-types {
- include "/usr/share/misc/mime.types"
-}
-
-```
-
-And reload `httpd(8)`.
-
-
-## great success
-
-
-Much thanks to my new friend on the Datasette Discord,
-the fantastic OpenBSD documentation,
-as always,
-and [lambda.cx](https://blog.lambda.cx/posts/openbsd-httpd-mime-types/)
-for writing a post almost identical to mine
-(except that his had nothing to do with RSS -
-he was fixing PDF serving,
-which should now be fixed on my site as well).
-
diff --git a/posts/mr-2021.md b/posts/mr-2021.md
@@ -1,183 +0,0 @@
-# Morning Report 08/23/2021
-
-Details modified, generalized, and otherwise fudged to be HIPAA-compliant.
-
-## HPI
-
-72F with chest pain, abdominal pain, and constipation.
-
-2-3mo weight loss, night sweats.
-
-2-3wk +perineal ?cyst, initially ttp and hurt to walk, but now nontender.
-
-~1wk constipation, BRB on TP.
-
-+crampy LLQ pain 8/10, x3-4 days, improves with positioning (supine with head raised somewhat, 3-4 pillows).
-
-+LUQ and left-sided chest pain x1-2 days, radiates to L arm, not related to exertion, lasts a few minutes.
-
-## OP Meds
-- duloxetine 60mg
-- ASA 81mg
-- melatonin 6mg
-- no notable allergies
-
-## PMSHx
-- TVH-BSO for fibroids and endometriosis (~20y ago)
-- hemorrhoids (no surgeries)
-- s/p Moderna COVID vaccine (~4wk ago)
-- UTD on mammograms, colonoscopies, no deviations from regular schedule
-
-## SHx
-- monogamous x45y, G2P2 sons, 6yo grandson, all healthy
-- never smoker
-- social EtOH, none this year
-- no non-Rx medicines
-- previously secretary
-- likes to DIY: painting, home crafts, gardening
-
-## FHx
-- M GM: uterine cancer (~40yo)
-- P GF: lung ca, unknown type (~70yo)
-
-## PE
-- VS: wnl
-- GEN: NAD
-- HEENT: no LAD
-- PULM: fine
-- CV: fine
-- ABD: NTND, +splenomegaly
-- GYN: 0.5cm lesion R side of anterior perineum, NT, freely mobile
-- NEURO: fine
-
-## Labs
-- Hgb 12.7
-- WBC 58.3
- - 0 blasts
- - 0 atypical lymphs
- - + slight L shift
-- Plt 490
-- BMP grossly wnl (gluc 202, [Cr fine](https://www.ashclinicalnews.org/viewpoints/editors-corner/illegitimi-epic-non-carborundum-dont-let-epic-bastards-grind/))
-- LFTs fine
-- Trop <0.01
-- urate 10.4
-- phos 5.0
-- LDH 330
-- fibrinogen 355
-
-## Other studies
-- EKG wnl
-- CT-PE -ve
-- CT a/p wwo
- - +10x7cm pelvic mass (central/R adnexum, exerting mass effect on sigmoid colon)
- - spleen ~20cm largest dimension w ?infarcts x2,
- - L internal iliac vein filling defects c/w nonocclusive DVT
-- PET/CT
- - splenomegaly with diffusely increased uptake, diffuse FDG uptake of axial and appendicular skeleton, mild uptake of abdominal pelvic lymph nodes, and minimal to mild uptake in the pelvic mass.
-
-## Further notes on hospital course
-- CEA 1.7 (wnl), CA-125 52 (-)
-- urate 9.5 5d later w IVF, given rasburicase 3mg x1 -> urate 3.8
-- phos similarly without movement, sevelamer eventually helpful
-- pelvic mass bx: smooth muscle
-- BMBx: hypercellular >90%, no blasts, +trilineage atypica > myeloid, MF-1 fibrosis.
-- JAK2 -ve, BCR/ABL -ve
-- NGS
- - BRAF 5% (MGUS, MM, hairy cell, hystiocytic/dendritic cell, solid tumors, therapy-related myeloid neoplasms)
- - KRAS 39% (MDS, AML, MDS/MPN inc CMML and JMML)
- - BCOR 49% (?, possibly germline since allele fraction ~50%)
- - BCORL1 48% (ditto)
- - EZH2 93% (?, likely germline w loss of heterozygosity)
-
-## And then...
-
-Diagnosis is... MDS/MPN/MF NOS.
-
-I.e., who knows.
-
-Started on hydroxyurea and decitabine, c/b recurrent bacteremia, so currently tx on hold.
-
----
-
-## TLS
-
-The big idea, and a few finer points.
-
-[![TLS](https://cdn.jamanetwork.com/ama/content_public/journal/oncology/937239/cpg180002fa.png?Expires=1632594426&Signature=y4M-w5gXSYJCAVMqGVEyfaPaqZocE9nGaWFnmr7GY7vuiD35l7dL-yJLWn4l3huTo4yBhri1nM0KjQ4dZBBjEYH5tPmKExEJ0D6V~WNou9Av-OEwhyQh79y9feHp790YWY6hTKRJJge958meDu~OmNl8Sl0Wn1N4buZZgVNMRdRds9fKbaDr4DhEdCbMgFbbLSeW9h8KIOm49Gog8FREQNntRaN1jILZgKPBTr9sUNv2BUiapZaLPO4teIf33LkJXcStx6o1VVsZJoP-G-sfMKG3ql1O~23E6LFJeirnMt5MYQdfk-LZlieuSw16HzqTXr-jBtOicDtyFzDJ9VcQ~g__&Key-Pair-Id=APKAIE5G5CRDK6RD3PGA =500x500 'JAMA Oncology 2018, TLS Review')](https://jamanetwork.com/journals/jamaoncology/fullarticle/2680750)
-
-### Cairo-Bishop classification system
-
-(Most of the following derived from
-[Chapter 4](https://www.asn-online.org/education/distancelearning/curricula/onco/Chapter4.pdf)
-of the American Society of Nephrology online
-[Onco-Nephrology curriculum](https://www.asn-online.org/education/distancelearning/curricula/onco/),
-which is good and great.)
-
-### Laboratory TLS
-
-Definition:
-Chemotherapy plus the two or more of the following
-within 3d before or 7d after initiation
-(so doesn't account for the spontaneous TLS seen in our patient).
-
-| Metabolite/Electrolyte | Criterion |
-| :----------------------- | :----------------------------------------: |
-| Uric Acid | >=8 mg/dL or 25% increase from baseline |
-| Potassium | >=6mEq/L or 25% increase from baseline |
-| Phosphorus | >=4.5mg/dL or 25% increase from baseline |
-| Calcium | 25% *decrease* from baseline |
-
-
-The "25% increase/decrease" part is contested,
-as it may not be clinically meaningful
-if the value stays within the normal range.
-
-### Clinical TLS
-
-| Laboratory TLS and one or more of |
-| :-------------------------------- |
-| creatinine >= 1.5 ULN (Note: just use AKI criteria) |
-| cardiac arrhythmia or sudden death |
-| seizure |
-
-- risk assessment
-
-### Treating TLS
-
-IVF, electrolytes, rasburicase.
-
-Rasburicase is the subject of a recent "Things We Do for No Reason."
-
-[Pay-walled article](https://www.journalofhospitalmedicine.com/jhospmed/article/241443/hospital-medicine/things-we-do-no-reasontm-rasburicase-adult-patients-tumor),
-[PDF made available by the authors](https://cdn.mdedge.com/files/s3fs-public/JHM01607424.PDF)
-
-TL;DR:
-the evidence is thin, but could be reasonable to
-- ppx w IVF and allopurinol for low-med risk,
-- use single 3mg dose rasburicase as ppx in high-risk disease (don't use weight-based dosing),
-- tx active TLS (laboratory or clinical) with aggressive fluid resuscitation and electrolyte mgmt,
-possibly single 3mg dose.
-
-Hard outcomes in support of rasburicase are generally lacking, e.g. consistently reducing renal injury, renal failure, length of stay.
-
-It also seems like the classification criteria need revamping,
-with a larger N.
-It's been a while.
-However, like redefining fever,
-it's difficult to get a clean slate,
-because we act on the established criteria so aggressively.
-
----
-
-## MDS/MPN overlap syndromes
-
-Not much to say here,
-except that the dx is not always clear-cut,
-even with BMBx and NGS data,
-so the clinical picture matters,
-and sometimes we have to shoot in the dark.
-
-
----
-
-Last updated: 2021-08-22
diff --git a/posts/ugbsd.md b/posts/ugbsd.md
@@ -1,46 +0,0 @@
-# Upgrading out-of-date OpenBSD installs
-
-
-First of all, don't do how I do.
-Upgrade your installs regularly.
-OpenBSD makes it very easy.
-
-But, if you do happen to get behind...
-
-`sysupgrade` is very likely to fail.
-
-
-## What happens when you try to upgrade a very old install?
-
-
-Lots of 404 errors.
-
-The `sysupgrade` utility tries to grab the next version of the OS from one of the many mirrors
-(the specific one your system will use is in `/etc/installurl`.)
-
-The default mirrors only keep the last 2 or 3 versions around,
-so when `sysupgrade` constructs the url and tries to hit it for downloads, it will fail.
-
-
-## Where to get old versions?
-
-
-There are a couple of mirrors that keep almost all the old versions around.
-
-<https://mirror.yandex.ru/pub/OpenBSD/>
-has files going back to OpenBSD 2.x - they seem like
-the most serious archivists, at least of the mirrors I looked at.
-
-<https://mirror.sjtu.edu.cn/OpenBSD/>
-has files going back to 6.5 as of this writing (2022-11-11),
-also not too shabby.
-
-Do a little `vi /etc/installurl` and change the link to one of the above,
-depending on how delinquent you've been.
-
-That should allow you to do serial `sysupgrade` commands until you catch up.
-
-When you get close to the current version,
-consider switching back to a closer mirror,
-both for faster installs
-and to be kind to the folks who just saved your bacon.
diff --git a/prism.css b/prism.css
@@ -1,190 +0,0 @@
-/* PrismJS 1.25.0
-https://prismjs.com/download.html#themes=prism-okaidia&languages=markup+css+clike+javascript+bash+git+python+r+toml&plugins=toolbar+copy-to-clipboard */
-/**
- * okaidia theme for JavaScript, CSS and HTML
- * Loosely based on Monokai textmate theme by http://www.monokai.nl/
- * @author ocodia
- */
-
-code[class*="language-"],
-pre[class*="language-"] {
- color: #f8f8f2;
- background: #000;
- text-shadow: 0 1px rgba(0, 0, 0, 0.3);
- font-family: "IBM Plex Mono", Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
- font-size: 90%;
- text-align: left;
- white-space: pre;
- word-spacing: normal;
- word-break: normal;
- word-wrap: normal;
- line-height: 1.25;
-
- -moz-tab-size: 4;
- -o-tab-size: 4;
- tab-size: 4;
-
- -webkit-hyphens: none;
- -moz-hyphens: none;
- -ms-hyphens: none;
- hyphens: none;
-}
-
-/* Code blocks */
-pre[class*="language-"] {
- padding: 1em;
- margin: .5em 0;
- overflow: auto;
- border-radius: 0.3em;
-}
-
-:not(pre) > code[class*="language-"],
-pre[class*="language-"] {
- background: #000;
-}
-
-/* Inline code */
-:not(pre) > code[class*="language-"] {
- padding: .1em;
- border-radius: .3em;
- white-space: normal;
-}
-
-.token.comment,
-.token.prolog,
-.token.doctype,
-.token.cdata {
- color: #8292a2;
-}
-
-.token.punctuation {
- color: #f8f8f2;
-}
-
-.token.namespace {
- opacity: .7;
-}
-
-.token.property,
-.token.tag,
-.token.constant,
-.token.symbol,
-.token.deleted {
- color: #f92672;
-}
-
-.token.boolean,
-.token.number {
- color: #ae81ff;
-}
-
-.token.selector,
-.token.attr-name,
-.token.string,
-.token.char,
-.token.builtin,
-.token.inserted {
- color: #a6e22e;
-}
-
-.token.operator,
-.token.entity,
-.token.url,
-.language-css .token.string,
-.style .token.string,
-.token.variable {
- color: #f8f8f2;
-}
-
-.token.atrule,
-.token.attr-value,
-.token.function,
-.token.class-name {
- color: #e6db74;
-}
-
-.token.keyword {
- color: #66d9ef;
-}
-
-.token.regex,
-.token.important {
- color: #fd971f;
-}
-
-.token.important,
-.token.bold {
- font-weight: bold;
-}
-.token.italic {
- font-style: italic;
-}
-
-.token.entity {
- cursor: help;
-}
-
-div.code-toolbar {
- position: relative;
-}
-
-div.code-toolbar > .toolbar {
- position: absolute;
- top: .3em;
- right: .2em;
- transition: opacity 0.3s ease-in-out;
- opacity: 0;
-}
-
-div.code-toolbar:hover > .toolbar {
- opacity: 1;
-}
-
-/* Separate line b/c rules are thrown out if selector is invalid.
- IE11 and old Edge versions don't support :focus-within. */
-div.code-toolbar:focus-within > .toolbar {
- opacity: 1;
-}
-
-div.code-toolbar > .toolbar > .toolbar-item {
- display: inline-block;
-}
-
-div.code-toolbar > .toolbar > .toolbar-item > a {
- cursor: pointer;
-}
-
-div.code-toolbar > .toolbar > .toolbar-item > button {
- background: none;
- border: 0;
- color: inherit;
- font: inherit;
- line-height: normal;
- overflow: visible;
- padding: 0;
- -webkit-user-select: none; /* for button */
- -moz-user-select: none;
- -ms-user-select: none;
-}
-
-div.code-toolbar > .toolbar > .toolbar-item > a,
-div.code-toolbar > .toolbar > .toolbar-item > button,
-div.code-toolbar > .toolbar > .toolbar-item > span {
- color: #bbb;
- font-size: .8em;
- padding: 0 .5em;
- background: #f5f2f0;
- background: rgba(224, 224, 224, 0.2);
- box-shadow: 0 2px 0 0 rgba(0,0,0,0.2);
- border-radius: .5em;
-}
-
-div.code-toolbar > .toolbar > .toolbar-item > a:hover,
-div.code-toolbar > .toolbar > .toolbar-item > a:focus,
-div.code-toolbar > .toolbar > .toolbar-item > button:hover,
-div.code-toolbar > .toolbar > .toolbar-item > button:focus,
-div.code-toolbar > .toolbar > .toolbar-item > span:hover,
-div.code-toolbar > .toolbar > .toolbar-item > span:focus {
- color: inherit;
- text-decoration: none;
-}
diff --git a/scripts/gnomad_startup b/scripts/gnomad_startup
@@ -1,5 +0,0 @@
-#!/usr/bin/env bash
-
-pip uninstall -y enum34
-
-#pip install gnomad
diff --git a/site/about.md b/site/about.md
@@ -0,0 +1,36 @@
+# about
+
+I'm a husband, father, physician, educator, and data scientist.
+
+
+## husband and father
+
+These are the most important roles I play.
+If we meet, and you're interested, we can talk about it.
+
+
+## physician
+
+Medical school at Cleveland Clinic Lerner College of Medicine
+of Case Western Reserve University,
+Class of 2020.
+
+Harrison Society member at Vanderbilt University,
+which includes internal medicine residency (2020-2022)
+and research/fellowship in hematology-oncology (2022-2026).
+
+
+## educator
+
+Harvard Macy Institute faculty, 2018-present.
+Health Care Education 2.0.
+
+
+## data scientist
+
+Machine learning and data science approaches to
+diagnosis and prognosis of blood cancers;
+healthcare disparities in hospital medicine;
+predictive modeling of hospital readmissions and length of stay.
+
+[Google Scholar profile](https://scholar.google.com/citations?user=Ng5AgXAAAAAJ)
diff --git a/contact.md b/site/contact.md
diff --git a/site/index.html b/site/index.html
@@ -0,0 +1 @@
+<br>
diff --git a/now.md b/site/now.md
diff --git a/site/posts/employee-wifi.md b/site/posts/employee-wifi.md
@@ -0,0 +1,218 @@
+# Set Up Enterprise Wifi on Arch Linux
+
+<time id="post-date">2021-09-17</time>
+
+<p id="post-excerpt">
+Most big institutions have guest and employee wifi networks.
+Guest wifi is usually fine, fast enough for the basics,
+but far inferior to employee wifi.
+On a custom-built OS, such as a fairly minimalist Linux distribution,
+getting the employee wifi to work
+can be a beast.
+
+This was a little tricky to get working
+but very worth it,
+so here's an outline,
+mostly for my own later benefit.
+</p>
+
+This post is specific to [VUMC](https://www.vumc.org),
+with the VUMCEmployee network.
+
+Similar steps should be applicable for other enterprise wifi users,
+though this post will unquestionably be out of date before long,
+and the intricacies of enterprise wifi are infinite.
+
+## VUMCGuest is fine
+
+As with other public networks at large institutions,
+VUMCGuest is just a little slow and finicky,
+and it's annoying to have to re-authenticate repeatedly
+to use all the HIPAA-compliant things.
+
+## VUMCEmployee is better
+
+I'll probably put a screenshot here at some point
+comparing speedtest scores.
+VUMCEmployee gives
+over 100 Mbps down,
+and around 100 up.
+
+It's also more stable,
+and latency is around 10ms.
+
+Most practical gain,
+other than faster everything:
+When I use VUMCGuest,
+the keyboard shortcut I use to
+launch and automatically login to Epic
+only works intermittently.
+On VUMCEmployee, it works reliably.
+No more typing!
+It's faster and, again, more reliable
+than tapping the badge-readers at the VUMC workstations.
+
+## Backend
+
+The personal networking stack
+of greatest beauty
+on Linux
+at this point is:
+
+`systemd-networkd` +`systemd-resolved` + `iwd`
+
+Disable and delete `NetworkManager`
+and other such nonsense,
+if you are unwise like me
+and installed conflicting and useless things.
+
+If you'd like a GUI, [iwgtk](https://github.com/J-Lentz/iwgtk) is nice,
+but the CLI shipped with `iwd` (`iwctl`)
+is intuitive, friendly, and well-documented.
+I keep the GUI version around for quickly checking on things
+via a keyboard shortcut,
+but use the CLI for any heavy lifting,
+which has thankfully become rare since landing on this setup.
+
+## Start with VUMCEmployeeSetup
+
+First, log on to the VUMCEmployeeSetup wifi.
+Then navigate to one of my favorite websites, <http://neverssl.com/>.
+This will force the redirect to the VUMCEmployee enrollment page
+(I also use this site for connecting to public wifi
+at airports, libraries, coffee shops, etc.).
+Agree to the terms and conditions.
+Then click the "Show all operating systems" link at the bottom,
+followed by the "Other Operating Systems" tab
+that pops up at the bottom of the list.
+
+The "Other Operating Systems" tab has
+three steps listed,
+which are simply the pieces that the
+various installers put together for you.
+The first two are downloads for certificates,
+and the third is a template.
+
+Finding this tab
+was the gold mine - initially I
+repackaged one of the other Linux installers for Arch,
+because I thought that (since there was an installer)
+the process must be complicated,
+and repackaging things from Debian-based systems
+for Arch-based systems is easy enough.
+The repackaged version of the installer
+was decent at first,
+but it turns out that
+the manual process is easier and more reliable.
+I also learned more about enterprise networks in the process,
+which was an added bonus
+(I'm honestly not sure about the
+sarcasm:sincerity ratio in the previous sentence).
+
+Download the `PEM` files listed under
+Steps 1 (root certificate) and
+2 (client certificate).
+
+## Make your own `iwd` profile
+
+Here's where it goes:
+`/var/lib/iwd/VUMCEmployee.8021x`
+
+Below are the contents,
+sensitive info redacted,
+then we'll go through some of the key parts
+and one nicety.
+
+```toml
+[IPv6]
+Enabled=true
+
+[Security]
+EAP-Method=PEAP
+EAP-Identity=username
+EAP-PEAP-CACert=embed:root_cert
+EAP-PEAP-ServerDomainMask=*.radius.service.vumc.org
+EAP-PEAP-Phase2-Method=MSCHAPV2
+EAP-PEAP-Phase2-Identity=username
+EAP-PEAP-Phase2-Password=password
+
+[Settings]
+AutoConnect=true
+
+[@pem@root_cert]
+-----BEGIN CERTIFICATE-----
+*lots of gobbledigook goes here*
+-----END CERTIFICATE-----
+```
+
+Most of these options are outlined in
+Step 3 from the VUMCEmployeeSetup,
+cross-referenced against the Arch Wiki page on `iwd`,
+subsection [Network configuration](https://wiki.archlinux.org/title/Iwd#EAP-PEAP),
+and the [`iwd` wiki proper](https://iwd.wiki.kernel.org/networkconfigurationsettings).
+
+An easy-to-miss step:
+The `EAP-PEAP-Phase2-Method` requirement for `MSCHAPV2`
+leads to another required install,
+check the wiki for current instructions.
+
+Put in your own username and password.
+
+My favorite trick in this file is
+directly embedding the root certificate
+in the line
+`EAP-PEAP-CACert=`
+with the syntax
+`embed:root_cert`
+(any name is fine,
+doesn't have to be `root_cert`,
+it's just a pointer).
+Then you add a definition of `root_cert` in a
+`[@pem@root_cert]` section.
+Insert the contents of the root certificate directly
+via copy-paste or `cat`, etc.
+
+Easiest method, as root:
+
+```shell
+cat /home/beau/dl/root_cert.PEM >> /var/lib/iwd/VUMCEmployee.8021x
+```
+
+With the direct embed method,
+you don't need to point to the root certificate file
+or keep it around at all.
+
+Needless to say,
+`VUMCEmployee.8021x`
+is a sensitive file and should be protected appropriately.
+However, this file or a version of it
+is what the automated tools would have made anyway,
+so there's no special risk here -
+AND since you did it all yourself
+you know there was no funny business
+coming from a black-box installer.
+
+## The other certificate (Client)
+
+I can't remember what I had to do with the client cert,
+probably added using the Chrome/Firefox certificate
+managers.
+
+I had to do this before when getting set up for VA remote access,
+the Arch Wiki comes through again with an article on
+[Common Access Cards](https://wiki.archlinux.org/title/Common_Access_Card)
+that includes instructions on adding certs to browsers.
+
+There's a chance it's not even needed?
+The [specification](https://iwd.wiki.kernel.org/networkconfigurationsettings)
+no longer supports
+adding a client cert field
+without a key,
+which I don't have,
+and do not, apparently, need
+(see the section "EAP-PEAP with tunneled EAP-MSCHAPV2").
+At any rate, this setup is working now
+and I won't futz with it further
+until something breaks.
+
+## -> ~~Profit~~ Prosper
diff --git a/site/posts/geocheatcode.md b/site/posts/geocheatcode.md
@@ -0,0 +1,196 @@
+# geocheatcode
+
+<time id="post-date">2022-04-22</time>
+
+<p id="post-excerpt">
+Here is background and code
+for a trick I use to get
+Google to give me best-in-class guesses
+for latitude and longitude,
+despite goofy and/or downright bad location searches.
+</p>
+
+## Map all the things
+
+I love maps.
+
+Several of my projects involve mapping things at scale.
+
+When you want to map a few things,
+you type searches into Google Maps
+and get addresses and/or latitudes and longitudes
+quickly and reliably.
+
+But what if you'd like to map 90,000 things
+whose locations you don't yet know?
+
+[Google](https://developers.google.com/maps)
+and
+[OpenStreetMap](https://www.openstreetmap.org/),
+as well as others,
+provide mapping services
+you can call programmatically from your software.
+You send in some query,
+such as "VUMC Internal Medicine,"
+and they return information
+relevant to that query,
+such as street address and
+latitude and longitude.
+Up to a certain number of queries per day or hour,
+the services are free,
+and since my work is academic,
+rather than real-time mapping for some
+for-profit app,
+I am happy to send in small batches
+to stay under the limits in the free tier.
+
+I've used these services to make large maps,
+and they work pretty well.
+
+*Pretty* well.
+
+## But mapping is hard
+
+Problems with these services:
+
+1. they expected well-formed and reasonable queries
+2. if they didn't know the answer, the guesses were often wildly off, or they would refuse to guess at all
+
+If I'm mapping 90,000 things,
+I'm going to write some code
+to go through each of those 90,000 things
+and ask the mapping services
+to kindly tell me what I want to know.
+Though I write sanitation code to clean up the 90,000 things,
+I'm not going to quality check each of those 90,000 things.
+Sometimes things among the 90,000 things are kinda nuts
+(misspelled, inclusive of extraneous data, oddly formatted),
+in idiosyncratic ways that are impossible to completely cover,
+no matter how much code I write to catch the weird cases.
+
+I would like a solution that is fairly tolerant of weirdnesses,
+and makes good guesses.
+
+## Google is really good at search
+
+I noticed that when I manually typed things
+into the Google Maps search bar,
+it forgave a myriad of sins
+and did a great job centering the map on its best guess.
+When I copied and pasted some of the weird things among the 90,000
+into the Google Maps search bar
+(the same things that made the
+official mapping services - including Google's -
+go all Poltergeist),
+*voila!*, the right answer appeared,
+success rates nearing 100%.
+
+I thought there must be a way to repeat this process with code,
+in a scalable way.
+
+Turns out there is, and it's easy.
+
+## `geocheatcode.py`
+
+```python
+
+from requests_html import HTMLSession
+
+session = HTMLSession()
+
+
+def google_lat_lon(query: str):
+
+ url = "https://www.google.com/maps/search/?api=1"
+ params = {}
+ params["query"] = query
+
+ r = session.get(url, params=params)
+
+ reg = "APP_INITIALIZATION_STATE=[[[{}]"
+ res = r.html.search(reg)[0]
+ lat = res.split(",")[2]
+ lon = res.split(",")[1]
+
+ return lat, lon
+
+
+extraneous = """ something something
+ the earth is banana shaped
+ latitude and longitude
+ wouldn't you like to know, maybe """
+
+relevant = """ Vanderbilt University Medical Center
+ Internal Medicine """
+
+query = extraneous + relevant
+
+lat, lon = google_lat_lon(query)
+
+print(
+ "Hello. "
+ "My name is Google. "
+ "I am really good at guessing what you meant. "
+ f"Your query was '{query}'. "
+ "Here are the coordinates you probably wanted. "
+ f"The latitude is {lat}, and the longitude is {lon}. "
+ "Don't believe me? "
+ "Here it is again, "
+ "in a format you can paste into the search bar: \n"
+ f"{lat}, {lon} \n"
+ "Told ya. "
+)
+
+```
+
+Despite having all that extra junk in the query,
+this returns the right answer.
+Because Google is many things good and evil,
+but of these one is certain:
+Google is *really* good at search.
+
+## How does the code work?
+
+If you inspect the source HTML
+on the Google Maps website
+after you search for something
+and it centers the map on its best guess,
+and you scroll way on down (or Ctrl-F search for it)
+you'll find `APP_INITIALIZATION_STATE`, which contains
+latitude and longitude for the place the map centered on.
+
+- [example search](https://www.google.com/maps?q=something+whose+latitude+and+longitude+you+would+like+to+know,+maybe+VUMC+Internal+Medicine)
+- [example source](view-source:https://www.google.com/maps/search/something+whose+latitude+and+longitude+you+would+like+to+know,+maybe+VUMC+Internal+Medicine/) (you have to copy and paste this link into a new tab manually, clicking won't work)
+
+I use the lovely
+[`requests-html`](https://docs.python-requests.org/projects/requests-html/en/latest/)
+Python library
+to send the query to Google,
+receive the response,
+and search through the response for the part I want to extract.
+Then I use a little standard Python
+to parse the extracted part and save the important bits.
+
+## With great power...
+
+Don't go crazy with this.
+
+The trick is good for
+leisurely automation
+of location retrieval
+when you have squirrelly queries.
+
+If you need real-time mapping of many things,
+you don't want this solution.
+Use the actual APIs,
+and work instead on formatting the queries properly
+before sending them to Google/OSM.
+
+Also, if you try to query too much/too quickly,
+Google will shut you out after a little while.
+Put a few seconds of delay between each request
+and run it overnight and/or in automated batches.
+
+## Know a better way?
+
+I'd love to know. Drop me a line.
diff --git a/site/posts/index.html b/site/posts/index.html
@@ -0,0 +1,5 @@
+<h1 id="title">Posts</h1>
+
+<p>You can subscribe to the <a href="/atom.xml">Atom feed</a> to receive updates.</p>
+
+<div id="posts-index"> </div>
diff --git a/site/posts/intake-2022-03-25.md b/site/posts/intake-2022-03-25.md
@@ -0,0 +1,49 @@
+# intake
+
+<time id="post-date">2022-03-25</time>
+
+## cc: trouble swallowing and weight loss
+
+
+28M w few weeks of trouble swallowing (gets stuck "right here," points to mid-sternum),
+solid/liquids same,
+gradual over months-weeks,
+some vomiting w/o specific timing.
+Sometimes has pain when not eating.
+20lb weight loss over months.
+No skin lesions.
+?thrush.
+
+## PMHx/PSHx
+
+dx BPD, no other dx or procedures
+
+
+## SHx
+
+- MSM w occasional use of protection, no PrEP
+- occ MJ use, no other substances
+- unemployed, lives w mom
+- no unusual hobbies or travel
+
+## PE
+
+HR 100, SBP 80 -> 100 w 500mL LR, AF
+cachectic (temporal, hypothenar wasting)
++skin tenting
+diffuse abd tenderness
+
+## w/u
+
+Hgb 10, MCV 88, WBC ~4, ANC 1500
+BMP wnl
+Alb 3.4
+HIV+, VL 15k, rest of STI -ve
+
+CXR wnl (AP and lateral)
+
+## dx
+
+<p id="post-excerpt">
+candidal esophagitis, achalasia, H Pylori PUD
+</p>
diff --git a/site/posts/mime.md b/site/posts/mime.md
@@ -0,0 +1,102 @@
+# fix MIME Types to unbreak RSS feeds served by OpenBSD's httpd(8)
+
+
+<time id="post-date">2022-11-13</time>
+
+
+## RSS is life - but mine was broken
+
+
+As of today (2022-11-13) my website lives on
+an OpenBSD server hosted at [vultr](https://vultr.com).
+
+It's great, delightfully simple and low-resource,
+robust, extendable, low-maintenance.
+
+<p id="post-excerpt">
+I've been getting back into RSS lately.
+Turns out, my own RSS feed was broken.
+</p>
+
+I knew it was janky, but would have had no idea how broken it was
+if not for the great folks
+on the [datasette](https://datasette.io) Discord,
+one of whom reached out to let me know my RSS link wasn't working.
+
+This could not stand!
+
+I've been meaning to fix my RSS feed anyway,
+and now I had a good reason.
+
+
+## fixing the file itself
+
+
+I ended up tearing out my previous RSS solution, [`rssg`](https://romanzolotarev.com/rssg.html),
+which is great but made some assumptions about my site's layout that aren't true.
+I could have rewritten the script,
+but I'm lazy and a little strapped for time,
+so I ended up replacing it with a hand-written RSS file.
+
+(The RSS spec is easy enough to write by hand,
+a little copy-paste and replace to add a new article -
+at some point I'll probably migrate to `hugo` or similar
+and hand off the feed creation to a more flexible script,
+but for now this works).
+
+After I was certain the file format was fine and had the info I wanted,
+I thought I was good.
+
+
+## fixing the MIME Type
+
+
+The kind soul who reached out to let me know the RSS feed was malformed
+reached out again to let me know he was now getting a MIME Type error.
+
+My feedreader of choice, `newsboat`,
+is very forgiving of what it accepts,
+and didn't throw any errors when I tested it.
+`FreshRSS`, on the other hand, is more strict,
+and the feed would fail even though the file itself was fine.
+
+I looked into it, and found out that `httpd(8)`
+only supports a handful of MIME Types by default,
+so my server was sending out `application/octet-stream`
+(a generic type) instead of the `rss+xml` type,
+and it was confusing the feedreader.
+
+
+## add all the types
+
+
+Thank goodness, and as usual in OpenBSD,
+there's a very easy way to add all the relevant types one might need.
+
+OpenBSD has an internal MIME declaration file you can link to from within `httpd.conf(5)`.
+
+Here's the relevant bit, just chuck this on the end of the conf file:
+
+```shell
+
+types {
+ include "/usr/share/misc/mime.types"
+}
+
+```
+
+And reload `httpd(8)`.
+
+
+## great success
+
+
+Much thanks to my new friend on the Datasette Discord,
+the fantastic OpenBSD documentation,
+as always,
+and [lambda.cx](https://blog.lambda.cx/posts/openbsd-httpd-mime-types/)
+for writing a post almost identical to mine
+(except that his had nothing to do with RSS -
+he was fixing PDF serving,
+which should now be fixed on my site as well).
+
diff --git a/site/posts/mr-2021.md b/site/posts/mr-2021.md
@@ -0,0 +1,186 @@
+# Morning Report 08/23/2021
+
+<time id="post-date">2021-08-23</time>
+
+Details modified, generalized, and otherwise fudged to be HIPAA-compliant.
+
+## HPI
+
+72F with chest pain, abdominal pain, and constipation.
+
+2-3mo weight loss, night sweats.
+
+2-3wk +perineal ?cyst, initially ttp and hurt to walk, but now nontender.
+
+~1wk constipation, BRB on TP.
+
++crampy LLQ pain 8/10, x3-4 days, improves with positioning (supine with head raised somewhat, 3-4 pillows).
+
++LUQ and left-sided chest pain x1-2 days, radiates to L arm, not related to exertion, lasts a few minutes.
+
+## OP Meds
+- duloxetine 60mg
+- ASA 81mg
+- melatonin 6mg
+- no notable allergies
+
+## PMSHx
+- TVH-BSO for fibroids and endometriosis (~20y ago)
+- hemorrhoids (no surgeries)
+- s/p Moderna COVID vaccine (~4wk ago)
+- UTD on mammograms, colonoscopies, no deviations from regular schedule
+
+## SHx
+- monogamous x45y, G2P2 sons, 6yo grandson, all healthy
+- never smoker
+- social EtOH, none this year
+- no non-Rx medicines
+- previously secretary
+- likes to DIY: painting, home crafts, gardening
+
+## FHx
+- M GM: uterine cancer (~40yo)
+- P GF: lung ca, unknown type (~70yo)
+
+## PE
+- VS: wnl
+- GEN: NAD
+- HEENT: no LAD
+- PULM: fine
+- CV: fine
+- ABD: NTND, +splenomegaly
+- GYN: 0.5cm lesion R side of anterior perineum, NT, freely mobile
+- NEURO: fine
+
+## Labs
+- Hgb 12.7
+- WBC 58.3
+ - 0 blasts
+ - 0 atypical lymphs
+ - + slight L shift
+- Plt 490
+- BMP grossly wnl (gluc 202, [Cr fine](https://www.ashclinicalnews.org/viewpoints/editors-corner/illegitimi-epic-non-carborundum-dont-let-epic-bastards-grind/))
+- LFTs fine
+- Trop <0.01
+- urate 10.4
+- phos 5.0
+- LDH 330
+- fibrinogen 355
+
+## Other studies
+- EKG wnl
+- CT-PE -ve
+- CT a/p wwo
+ - +10x7cm pelvic mass (central/R adnexum, exerting mass effect on sigmoid colon)
+ - spleen ~20cm largest dimension w ?infarcts x2,
+ - L internal iliac vein filling defects c/w nonocclusive DVT
+- PET/CT
+ - splenomegaly with diffusely increased uptake, diffuse FDG uptake of axial and appendicular skeleton, mild uptake of abdominal pelvic lymph nodes, and minimal to mild uptake in the pelvic mass.
+
+## Further notes on hospital course
+- CEA 1.7 (wnl), CA-125 52 (-)
+- urate 9.5 5d later w IVF, given rasburicase 3mg x1 -> urate 3.8
+- phos similarly without movement, sevelamer eventually helpful
+- pelvic mass bx: smooth muscle
+- BMBx: hypercellular >90%, no blasts, +trilineage atypica > myeloid, MF-1 fibrosis.
+- JAK2 -ve, BCR/ABL -ve
+- NGS
+ - BRAF 5% (MGUS, MM, hairy cell, hystiocytic/dendritic cell, solid tumors, therapy-related myeloid neoplasms)
+ - KRAS 39% (MDS, AML, MDS/MPN inc CMML and JMML)
+ - BCOR 49% (?, possibly germline since allele fraction ~50%)
+ - BCORL1 48% (ditto)
+ - EZH2 93% (?, likely germline w loss of heterozygosity)
+
+## And then...
+
+<p id="post-excerpt">
+Diagnosis is... MDS/MPN/MF NOS.
+i.e., who knows.
+</p>
+
+Started on hydroxyurea and decitabine, c/b recurrent bacteremia, so currently tx on hold.
+
+---
+
+## TLS
+
+The big idea, and a few finer points.
+
+[![TLS](https://cdn.jamanetwork.com/ama/content_public/journal/oncology/937239/cpg180002fa.png?Expires=1632594426&Signature=y4M-w5gXSYJCAVMqGVEyfaPaqZocE9nGaWFnmr7GY7vuiD35l7dL-yJLWn4l3huTo4yBhri1nM0KjQ4dZBBjEYH5tPmKExEJ0D6V~WNou9Av-OEwhyQh79y9feHp790YWY6hTKRJJge958meDu~OmNl8Sl0Wn1N4buZZgVNMRdRds9fKbaDr4DhEdCbMgFbbLSeW9h8KIOm49Gog8FREQNntRaN1jILZgKPBTr9sUNv2BUiapZaLPO4teIf33LkJXcStx6o1VVsZJoP-G-sfMKG3ql1O~23E6LFJeirnMt5MYQdfk-LZlieuSw16HzqTXr-jBtOicDtyFzDJ9VcQ~g__&Key-Pair-Id=APKAIE5G5CRDK6RD3PGA =500x500 'JAMA Oncology 2018, TLS Review')](https://jamanetwork.com/journals/jamaoncology/fullarticle/2680750)
+
+### Cairo-Bishop classification system
+
+(Most of the following derived from
+[Chapter 4](https://www.asn-online.org/education/distancelearning/curricula/onco/Chapter4.pdf)
+of the American Society of Nephrology online
+[Onco-Nephrology curriculum](https://www.asn-online.org/education/distancelearning/curricula/onco/),
+which is good and great.)
+
+### Laboratory TLS
+
+Definition:
+Chemotherapy plus the two or more of the following
+within 3d before or 7d after initiation
+(so doesn't account for the spontaneous TLS seen in our patient).
+
+| Metabolite/Electrolyte | Criterion |
+| :----------------------- | :----------------------------------------: |
+| Uric Acid | >=8 mg/dL or 25% increase from baseline |
+| Potassium | >=6mEq/L or 25% increase from baseline |
+| Phosphorus | >=4.5mg/dL or 25% increase from baseline |
+| Calcium | 25% *decrease* from baseline |
+
+
+The "25% increase/decrease" part is contested,
+as it may not be clinically meaningful
+if the value stays within the normal range.
+
+### Clinical TLS
+
+| Laboratory TLS and one or more of |
+| :-------------------------------- |
+| creatinine >= 1.5 ULN (Note: just use AKI criteria) |
+| cardiac arrhythmia or sudden death |
+| seizure |
+
+- risk assessment
+
+### Treating TLS
+
+IVF, electrolytes, rasburicase.
+
+Rasburicase is the subject of a recent "Things We Do for No Reason."
+
+[Pay-walled article](https://www.journalofhospitalmedicine.com/jhospmed/article/241443/hospital-medicine/things-we-do-no-reasontm-rasburicase-adult-patients-tumor),
+[PDF made available by the authors](https://cdn.mdedge.com/files/s3fs-public/JHM01607424.PDF)
+
+TL;DR:
+the evidence is thin, but could be reasonable to
+- ppx w IVF and allopurinol for low-med risk,
+- use single 3mg dose rasburicase as ppx in high-risk disease (don't use weight-based dosing),
+- tx active TLS (laboratory or clinical) with aggressive fluid resuscitation and electrolyte mgmt,
+possibly single 3mg dose.
+
+Hard outcomes in support of rasburicase are generally lacking, e.g. consistently reducing renal injury, renal failure, length of stay.
+
+It also seems like the classification criteria need revamping,
+with a larger N.
+It's been a while.
+However, like redefining fever,
+it's difficult to get a clean slate,
+because we act on the established criteria so aggressively.
+
+---
+
+## MDS/MPN overlap syndromes
+
+Not much to say here,
+except that the dx is not always clear-cut,
+even with BMBx and NGS data,
+so the clinical picture matters,
+and sometimes we have to shoot in the dark.
+
+
+---
+
+Last updated: 2021-08-22
diff --git a/site/posts/ugbsd.md b/site/posts/ugbsd.md
@@ -0,0 +1,50 @@
+# Upgrading out-of-date OpenBSD installs
+
+
+<time id="post-date">2022-11-11</time>
+
+<p id="post-excerpt">
+First of all, don't do how I do.
+Upgrade your installs regularly.
+OpenBSD makes it very easy.
+</p>
+
+But, if you do happen to get behind...
+
+`sysupgrade` is very likely to fail.
+
+
+## What happens when you try to upgrade a very old install?
+
+
+Lots of 404 errors.
+
+The `sysupgrade` utility tries to grab the next version of the OS from one of the many mirrors
+(the specific one your system will use is in `/etc/installurl`.)
+
+The default mirrors only keep the last 2 or 3 versions around,
+so when `sysupgrade` constructs the url and tries to hit it for downloads, it will fail.
+
+
+## Where to get old versions?
+
+
+There are a couple of mirrors that keep almost all the old versions around.
+
+<https://mirror.yandex.ru/pub/OpenBSD/>
+has files going back to OpenBSD 2.x - they seem like
+the most serious archivists, at least of the mirrors I looked at.
+
+<https://mirror.sjtu.edu.cn/OpenBSD/>
+has files going back to 6.5 as of this writing (2022-11-11),
+also not too shabby.
+
+Do a little `vi /etc/installurl` and change the link to one of the above,
+depending on how delinquent you've been.
+
+That should allow you to do serial `sysupgrade` commands until you catch up.
+
+When you get close to the current version,
+consider switching back to a closer mirror,
+both for faster installs
+and to be kind to the folks who just saved your bacon.
diff --git a/site/style.css b/site/style.css
@@ -0,0 +1,296 @@
+/** Variables **/
+
+:root
+{
+ --Canvas-bg: #201b1b;
+ --Canvas-color: #fff;
+ --Pre-bg: #151515;
+ --Pre-color: #fff;
+ --Pre-border: #222;
+ --Main-color: #f2f2f2;
+ --Code-color: #2adba4;
+ --Accent1-color: #2adba4; /* green */
+ --Accent2-color: #368aeb; /* blue */
+ --Highlight-num: #43a60d;
+ --Highlight-esc: #dfad06;
+ --Highlight-str: #f77f1d;
+ --Highlight-pps: #458759;
+ --Highlight-slc: #a0a0a0;
+ --Highlight-com: #a0a0a0;
+ --Highlight-ppc: #94e39;
+ --Highlight-opt: #ffffff;
+ --Highlight-ipl: #4dc987;
+ --Highlight-lin: #555555;
+ --Highlight-kwa: #d9396a;
+ --Highlight-kwb: #8655e7;
+ --Highlight-kwc: #ffffff;
+ --Highlight-kwd: #00a48f;
+}
+
+img {
+ display: inline-block;
+ max-width: 100%;
+}
+
+pre, code, sample {
+ white-space: pre-wrap;
+ hyphens: none;
+}
+
+table {
+ max-width: 100%;
+}
+
+html {
+ font-size: 70.5%;
+}
+
+body {
+ line-height: 1.5;
+ font-size: 1.6rem;
+ max-width: 38em;
+ margin: auto;
+ padding: 13px;
+}
+
+/* Structural blocks */
+
+#page-wrapper
+{
+ max-width: 88rem;
+
+ padding: 1rem 2rem 2rem;
+
+ margin-left: auto;
+ margin-right: auto;
+}
+
+
+/* Visual styles */
+
+html
+{
+ background: var(--Canvas-bg);
+ color: var(--Canvas-color);
+}
+
+header
+{
+ text-align: center;
+}
+
+::selection
+{
+ background: var(--Selection-bg);
+ color: var(--Selection-color);
+}
+
+/* shared gradient border */
+nav, footer
+{
+ border-width: 0;
+ border-style: solid;
+ border-color: var(--Accent1-color);
+ border-image: linear-gradient(to right, var(--Accent1-color), var(--Accent2-color)) 2;
+}
+
+nav
+{
+ font-size: 1.8rem;
+ border-bottom-width: 2px;
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+ word-spacing: 5px;
+ text-align: center;
+}
+
+main
+{
+ color: var(--Main-color);
+ text-rendering: optimizeLegibility;
+}
+
+footer
+{
+ text-align: center;
+ padding-top: 1rem;
+ margin-top: 1rem;
+ border-top-width: 2px;
+}
+
+div#banner-text
+{
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+}
+
+.banner
+{
+ display: flex;
+ flex-direction: row;
+ gap: 1rem;
+ justify-content: center;
+ align-items: center;
+ line-height: 1;
+}
+
+.banner-title
+{
+ margin: 0;
+ font-size: 3.6rem;
+ font-weight: 200;
+ letter-spacing: 0.425em;
+ /* letter-spacing is applied even to the last letter,
+ so for proper centering, we have to remove it ourselves, sadly */
+ margin-right: -0.425em;
+}
+
+.banner-title a
+{
+ text-decoration:none;
+ color: var(--Main-color);
+}
+
+.banner-title a:hover
+{
+ text-decoration:none;
+ color: var(--Accent1-color);
+}
+
+a {
+ color: var(--Accent2-color);
+}
+
+a:hover, a:focus
+{
+ text-decoration: underline;
+ color: var(--Accent1-color);
+}
+
+a.nav-active {
+ color: var(--Accent1-color);
+ font-weight: 200;
+}
+
+nav a { text-decoration: none; }
+
+a.here { text-decoration: none; }
+
+hr
+{
+ border: 0 none;
+ color: var(--Accent2-color);
+ background-color: currentColor;
+ height: 2px;
+}
+
+hr.footnotes { width: 40%; }
+
+a.footnote
+{
+ text-decoration: none;
+ margin-right: 0.4rem;
+}
+
+ul.toc { list-style: none; }
+
+h5 { font-size: 1.6rem; }
+h6 { font-size: 1.4rem; }
+
+code, kbd, samp
+{
+ color: var(--Code-color);
+}
+
+
+/** Typography */
+
+@supports (hyphens: auto)
+{
+ main
+ {
+ text-align: justify;
+ hyphens: auto;
+ }
+}
+
+body
+{
+ font-family: system-ui, sans-serif;
+ font-weight: 200;
+}
+
+table, th, td
+{
+ border: 1px solid var(--Accent1-color);
+ border-collapse: collapse;
+}
+
+th, td
+{
+ padding: 0.5em;
+ hyphens: none;
+ text-align: left;
+}
+
+.centered { text-align: center; }
+
+/* Style definition file generated by highlight 3.52, http://www.andre-simon.de/ */
+/* highlight theme: Kwrite Editor */
+body.hl { background-color: #e0eaee; }
+pre.hl { color: #000000; background-color:#e0eaee; font-size: 10pt; font-family: monospace; }
+.hl.num { color: var(--Highlight-num); }
+.hl.esc { color: var(--Highlight-esc); }
+.hl.str { color: var(--Highlight-str); }
+.hl.pps { color: var(--Highlight-pps); }
+.hl.slc { color: var(--Highlight-slc); font-style: italic; }
+.hl.com { color: var(--Highlight-com); font-style: italic; }
+.hl.ppc { color: var(--Highlight-ppc); }
+.hl.opt { color: var(--Highlight-opt); }
+.hl.ipl { color: var(--Highlight-ipl); }
+.hl.lin { color: var(--Highlight-lin); }
+.hl.kwa { color: var(--Highlight-kwa); font-weight: bold; }
+.hl.kwb { color: var(--Highlight-kwb); }
+.hl.kwc { color: var(--Highlight-kwc); font-weight: bold; }
+.hl.kwd { color: var(--Highlight-kwd); }
+
+pre
+{
+ box-sizing: border-box;
+ overflow: auto;
+ padding: 2rem;
+ margin-left: -2rem;
+ margin-right: -2rem;
+ border: 1px solid var(--Pre-border);
+ background: var(--Pre-bg);
+ color: var(--Pre-color);
+}
+
+pre code
+{
+ color: inherit;
+}
+
+@media screen and (max-width: 34em)
+{
+ .banner, .banner-title
+ {
+ display: block;
+ text-align: center;
+ margin: auto;
+ }
+ .banner
+ {
+ margin-bottom: 2rem;
+ }
+}
+
+@media screen and (max-width: 28em)
+{
+ .banner-title
+ {
+ font-size: 3.2rem;
+ }
+}
diff --git a/wants.md b/site/wants.md
diff --git a/soupault.toml b/soupault.toml
@@ -0,0 +1,145 @@
+[settings]
+strict = true
+verbose = true
+debug = false
+site_dir = "site"
+build_dir = "build"
+default_template_file = "templates/main.html"
+plugin_discovery = true
+plugin_dirs = ["plugins"]
+page_file_extensions = ["html", "md"]
+ignore_extensions = ["draft"]
+pretty_print_html = true
+clean_urls = true
+
+# Treat files as content to insert in the template,
+# unless they have an <html> element in them.
+generator_mode = true
+complete_page_selector = "html"
+
+# The content will be inserted into its <main> element,
+# after its last already existing child.
+default_content_selector = "main"
+default_content_action = "append_child"
+
+# Set the document type to HTML5, unless the page already has
+# a doctype declaration.
+doctype = "<!DOCTYPE html>"
+keep_doctype = true
+
+[preprocessors]
+md = 'pandoc -f commonmark+smart -t html --no-highlight --lua-filter=helpers/cmark-code-blocks.lua'
+
+[custom_options]
+site_url = "https://soupault.app"
+
+[index]
+index = true
+sort_descending = true
+sort_by = "date"
+strip_tags = false
+dump_json = "index.json"
+section = "posts"
+date_formats = ["%F"]
+
+
+[index.fields.title]
+selector = ["h1"]
+
+[index.fields.date]
+selector = ["time#post-date", "time"]
+extract_attribute = "datetime"
+fallback_to_content = true
+
+[index.fields.excerpt]
+selector = ["p#post-excerpt", "p"]
+
+[index.views.posts]
+index_selector = "#posts-index"
+sort_by = "date"
+sort_type = "calendar"
+
+paginate = true
+items_per_page = 5
+
+page_navigation_template = """
+ <hr>
+ {% if prev_url %}
+ <a href="{{prev_url}}">← newer</a>
+ {% endif %}
+ {# There may not be a next page #}
+ {% if next_url %}
+ <a href="{{next_url}}">older →</a>
+ {% endif %}
+ </div>
+ """
+page_navigation_selector = "#page-navigation"
+
+index_template = """
+ {% for e in entries %}
+ <h2><a href="{{e.url}}">{{e.title}}</a></h2>
+ <p><strong>Last update:</strong> {{e.date}}.</p>
+ <p>{{e.excerpt}}</p>
+ <a href="{{e.url}}">Read more</a>
+ {% endfor %}
+ """
+
+[widgets.escape-html-in-pre]
+widget = "escape-html"
+selector = ".raw-html"
+
+[widgets.highlight]
+after = "escape-html-in-pre"
+widget = "preprocess_element"
+selector = '*[class^="language-"]'
+command = 'highlight -O html -f --syntax=$(echo $ATTR_CLASS | sed -e "s/language-//")'
+
+# Add tabindex="0" to code snippets to aid keyboard, screen reader navigation
+[widgets.set-tab-index-for-code-snippets]
+widget = "set-tab-index"
+selectors = ["pre"]
+tab_index = 0
+
+[widgets.footnotes]
+widget = "footnotes"
+selector = "div#footnotes"
+footnote_selector = ["fn", ".footnote"]
+footnote_link_class = "footnote"
+back_links = true
+link_id_prepend = "footnote-"
+back_link_id_append = "-ref"
+
+[widgets.header]
+widget = "include"
+file = "templates/header.html"
+selector = "head"
+
+[widgets.nav]
+widget = "include"
+file = "templates/nav.html"
+selector = "nav"
+
+[widgets.footer]
+ widget = "include"
+ file = "templates/footer.html"
+ selector = "footer"
+
+[widgets.highlight-active-link]
+ after = ["footer"]
+ widget = "section-link-highlight"
+ selector = "nav"
+ active_link_class = "nav-active"
+
+[widgets.atom]
+widget = "atom"
+page = "posts/index.html"
+use_section = "posts"
+
+delete_elements = [".footnote", "a"]
+
+feed_file = "atom.xml"
+feed_author = "beau hilton"
+feed_author_email = "beau@beauhilton.com"
+feed_title = "beauhilton"
+feed_subtitle = "beau's website"
+feed_logo = "🏕️"
diff --git a/stop b/stop
@@ -0,0 +1,13 @@
+#!/bin/bash
+touch temp.text
+lsof -n -i4TCP:$1 | awk '{print $2}' > temp.text
+pidToStop=`(sed '2q;d' temp.text)`
+> temp.text
+if [[ -n $pidToStop ]]
+then
+kill -9 $pidToStop
+echo "Congrates!! $1 is stopped."
+else
+echo "Sorry nothing running on above port"
+fi
+rm temp.text
diff --git a/style.css b/style.css
@@ -1,204 +0,0 @@
-
-@import "fonts.css";
-
-body {
- font-family: "IBM Plex Sans", sans-serif;
- color: #474747;
- background-color: #ffffff;
- line-height: 1.45;
- font-size: 106.3%;
- margin: 0 auto;
- padding: 0.5em;
- max-width: 40em
-}
-
-a{
- color:#0064e4;
- font-weight: 400;
-}
-
-a:hover{
- color:#1d9700;
- font-weight: 600;
-}
-
-a:visited{
- color:#7f51d6
-}
-
-section{
- margin-bottom:1em
-}
-
-table td {
- padding: 0 0.4em;
-}
-
-td.num {
- text-align: right;
-}
-
-section#masthead{
- margin-bottom:2em;
-}
-
-section#masthead h1{
- margin:1em;
- margin-top:1em;
- font-size:3em;
- line-height:1.5em;
- max-width:50em;
- font-weight: 400;
-}
-section#masthead h1 a{
- color:#000;
- text-decoration:none;
-}
-
-section#masthead h1 a:hover{
- color:#70b433;
-}
-
-section#masthead h1 a:visited{
- color:#000;
- text-decoration:none;
-}
-
-h1, h2,h3,h4{
- margin-top:2em;
- font-size:1.5em;
- line-height:1.5em;
- font-weight: 400;
-}
-
-h5{ /* used only for the index page */
- margin:2em;
- font-size:2em;
- line-height:0.1em;
- font-weight: 400;
-}
-
-@media screen and (max-width: 800px){
- section#masthead
- div.presentation_summary img{
- display:block
- }
-}
-@media screen and (min-width: 800px){
- section#masthead
- div.presentation_summary img{
- float:right
- }
-}
-@media (min-width: 400px) {
- body {
- font-size: 118.8%;
- }
-}
-@media (min-width: 500px) {
- body {
- font-size: 118.8%;
- line-height: 1.55;
- }
-}
-@media (min-width: 600px) {
- body {
- font-size: 118.8%;
- line-height: 1.66;
- }
-}
-@media (min-width: 800px) {
- body {
- font-size: 131.3%;
- }
-}
-@media (min-width: 1030px) {
- body {
- font-size: 137.5%;
- }
-}
-@media (min-width: 1250px) {
- body {
- font-size: 143.8%;
- }
-}
-@media (min-width: 1920px) {
- body {
- font-size: 1.563em;
- }
-}
-.container,
-.postheader {
- width: 90%;
- margin: 0 auto;
- max-width: 1360px;
-}
-.container--wide {
- max-width: 1600px;
-}
-.container:after,
-.centered:after {
- content: "";
- display: table;
- clear: both;
-}
-.centered {
- max-width: 34em;
- margin: 0 auto;
- clear: both;
- width: 90%;
-}
-@media (min-width: 600px) {
- .centered {
- width: 84%;
- }
-}
-.centered.wider {
- max-width: calc(34em + 2em);
-}
-.centered-block {
- display: block;
- overflow: hidden;
-}
-@media(prefers-color-scheme:dark){
- body{
- background-color:#181818;
- color:#b9b9b9
- }
-
- code{
- font-size:.9em;
- background: #414449;
- padding: 0 2px;
- border-radius: 4px
- }
-
- a{
- color:#368aeb
- }
-
- a:hover{
- color:#70b433
- }
-
- a:visited{
- color:#a580e2
- }
- .response{
- background-color:#444
- }
-
- h1,h2,h3,h5{
- color:#368aeb
- }
-
- section#masthead h1 a{
- color:#dedede
- }
-
- section#masthead h1 a:visited{
- color:#dedede;
- text-decoration:none;
- }
-}
-
diff --git a/templates/footer.html b/templates/footer.html
diff --git a/templates/header.html b/templates/header.html
@@ -0,0 +1,8 @@
+<link rel="stylesheet" href="/style.css" type="text/css">
+
+ <meta charset="utf-8">
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
+ <link rel="stylesheet" type="text/css" href="/style.css" />
+ <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Cstyle%3E %23m %7B opacity:0; %7D%0A@media (prefers-color-scheme: dark) %7B %23m %7B opacity:1; %7D %23e %7B opacity:0 %7D%0A%7D %3C/style%3E%3Ctext id='m' y='.9em' font-size='90'%3E🏕️%3C/text%3E%3Ctext id='e' y='.9em' font-size='90'%3E🌞%3C/text%3E%3C/svg%3E">
+ <title> </title>
diff --git a/templates/main.html b/templates/main.html
@@ -0,0 +1,19 @@
+<html lang="en">
+
+ <head></head>
+ <body>
+ <div id="page-wrapper">
+ <div id="header" role="banner">
+ <header class="banner">
+ <div id="banner-text">
+ <span class="banner-title"><a href="/">beauhilton</a></span>
+ </div>
+ </header>
+ <nav> </nav>
+ </div>
+ <main> </main>
+ <div id="footnotes"> </div>
+ <footer> </footer>
+ </div>
+ </body>
+</html>
diff --git a/templates/nav.html b/templates/nav.html
@@ -0,0 +1,8 @@
+<a href="/about">about</a>
+<a href="/now">now</a>
+<a href="/posts">posts</a>
+<a href="https://notes.beauhilton.com">notes</a>
+<a href="https://talks.beauhilton.com">talks</a>
+<a href="https://git.beauhilton.com">git</a>
+<a href="/contact">contact</a>
+<a href="/feed.xml">rss</a>