site

Unnamed repository; edit this file 'description' to name the repository.
git clone https://git.beauhilton.com/site.git
Log | Files | Refs

commit f55e549c2e9e35a66a2d4c8c69a8cfc032aafa70
parent be108d16b1b3e5d4d961404269e281ae89b26db5
Author: Beau <cbeauhilton@gmail.com>
Date:   Thu,  1 Dec 2022 13:58:28 -0600

migrate to soupault

Diffstat:
D_footer.html | 9---------
D_header.html | 16----------------
Dabout.md | 26--------------------------
Dfeed.xml | 42------------------------------------------
Dfonts.css | 95-------------------------------------------------------------------------------
Ahelpers/cmark-code-blocks.lua | 28++++++++++++++++++++++++++++
Dimg/logo.png | 0
Aindex.json | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Dindex.md | 9---------
Dinfo.md | 3---
Djs/prism.js | 14--------------
Djs/tab-title.js | 5-----
Aplugins/atom.lua | 89+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplugins/escape-html.lua | 38++++++++++++++++++++++++++++++++++++++
Aplugins/reading-time.lua | 62++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplugins/section-link-highlight.lua | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Aplugins/set-tab-index.lua | 28++++++++++++++++++++++++++++
Dposts.md | 14--------------
Dposts/employee-wifi.md | 207-------------------------------------------------------------------------------
Dposts/geocheatcode.md | 193-------------------------------------------------------------------------------
Dposts/intake-2022-03-25.md | 44--------------------------------------------
Dposts/mime.md | 95-------------------------------------------------------------------------------
Dposts/mr-2021.md | 183-------------------------------------------------------------------------------
Dposts/ugbsd.md | 46----------------------------------------------
Dprism.css | 190-------------------------------------------------------------------------------
Dscripts/gnomad_startup | 5-----
Asite/about.md | 36++++++++++++++++++++++++++++++++++++
Rcontact.md -> site/contact.md | 0
Asite/index.html | 1+
Rnow.md -> site/now.md | 0
Asite/posts/employee-wifi.md | 218+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/posts/geocheatcode.md | 196+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/posts/index.html | 5+++++
Asite/posts/intake-2022-03-25.md | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Asite/posts/mime.md | 102+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/posts/mr-2021.md | 186+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/posts/ugbsd.md | 50++++++++++++++++++++++++++++++++++++++++++++++++++
Asite/style.css | 296+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rwants.md -> site/wants.md | 0
Asoupault.toml | 145+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Astop | 13+++++++++++++
Dstyle.css | 204-------------------------------------------------------------------------------
Atemplates/footer.html | 0
Atemplates/header.html | 8++++++++
Atemplates/main.html | 19+++++++++++++++++++
Atemplates/nav.html | 8++++++++
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("&", "&amp;"):gsub("<", "&lt;"):gsub(">", "&gt;") + + 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,"&amp;").replace(/</g,"&lt;").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,"&quot;")+'"';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(/&amp;/,"&"))}),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>