Added all posts

This commit is contained in:
Eduard Urbach 2024-04-04 11:37:35 +02:00
parent 5e2141941b
commit ac8bab9e38
Signed by: akyoto
GPG Key ID: C874F672B1AF20C0
23 changed files with 3218 additions and 64 deletions

97
App.go
View File

@ -4,6 +4,8 @@ import (
"bytes"
"fmt"
"os"
"slices"
"sort"
"strings"
"git.akyoto.dev/go/markdown"
@ -13,12 +15,12 @@ import (
type App struct {
html string
posts map[string]string
posts map[string]*Post
}
func (app *App) Init() {
app.html = mustLoadClean("public/app.html")
css := mustLoadClean("public/app.css")
app.html = loadClean("public/app.html")
css := loadClean("public/app.css")
app.html = strings.Replace(app.html, "{head}", fmt.Sprintf("{head}<style>%s</style>", css), 1)
app.posts = loadPosts("posts")
}
@ -56,31 +58,59 @@ func (app *App) Run() {
}
s.Get("/", func(ctx web.Context) error {
md := bytes.Buffer{}
html := bytes.Buffer{}
html.WriteString(`<h2>Blog</h2><ul class="blog">`)
articles := []*Post{}
for slug := range app.posts {
fmt.Fprintf(&md, "- [%s](/%s)\n", slug, slug)
for _, post := range app.posts {
if !post.Published || !slices.Contains(post.Tags, "article") {
continue
}
articles = append(articles, post)
}
return render(ctx, "<title>akyoto.dev</title>", markdown.Render(md.String()))
sort.Slice(articles, func(i, j int) bool {
return articles[i].Created > articles[j].Created
})
for _, post := range articles {
fmt.Fprintf(&html, `<li><a href="/%s">%s</a><time datetime="%s">%s</time></li>`, post.Slug, post.Title, post.Created, post.Created[:len("YYYY")])
}
html.WriteString(`</ul>`)
return render(ctx, "<title>akyoto.dev</title>", html.String())
})
s.Get("/:post", func(ctx web.Context) error {
post := ctx.Request().Param("post")
return render(ctx, "<title>akyoto.dev</title>", app.posts[post])
slug := ctx.Request().Param("post")
post := app.posts[slug]
head := fmt.Sprintf(`<title>%s</title><meta name="keywords" content="%s">`, post.Title, strings.Join(post.Tags, ","))
content := ""
if slices.Contains(post.Tags, "article") {
content = fmt.Sprintf(
`<article><header><h1>%s</h1><time datetime="%s">%s</time></header>%s</article>`,
post.Title,
post.Created,
post.Created[:len("YYYY-MM-DD")],
markdown.Render(post.Content),
)
} else {
content = fmt.Sprintf(
`<h2>%s</h2>%s`,
post.Title,
markdown.Render(post.Content),
)
}
return render(ctx, head, content)
})
s.Run(":8080")
}
func mustLoadClean(path string) string {
data := mustLoad(path)
data = strings.ReplaceAll(data, "\t", "")
data = strings.ReplaceAll(data, "\n", "")
return data
}
func mustLoad(path string) string {
func load(path string) string {
dataBytes, err := os.ReadFile(path)
if err != nil {
@ -90,32 +120,9 @@ func mustLoad(path string) string {
return string(dataBytes)
}
func loadPosts(directory string) map[string]string {
entries, err := os.ReadDir(directory)
if err != nil {
panic(err)
}
posts := map[string]string{}
for _, entry := range entries {
fileName := entry.Name()
if !strings.HasSuffix(fileName, ".md") {
continue
}
baseName := strings.TrimSuffix(fileName, ".md")
content := mustLoad("posts/" + fileName)
if strings.HasPrefix(content, "---\n") {
end := strings.Index(content[4:], "---\n") + 4
content = content[end+4:]
}
posts[baseName] = markdown.Render(content)
}
return posts
func loadClean(path string) string {
data := load(path)
data = strings.ReplaceAll(data, "\t", "")
data = strings.ReplaceAll(data, "\n", "")
return data
}

78
Post.go Normal file
View File

@ -0,0 +1,78 @@
package main
import (
"os"
"strings"
)
type Post struct {
Slug string
Content string
Title string
Tags []string
Created string
Published bool
}
func loadPosts(directory string) map[string]*Post {
entries, err := os.ReadDir(directory)
if err != nil {
panic(err)
}
posts := map[string]*Post{}
for _, entry := range entries {
fileName := entry.Name()
if !strings.HasSuffix(fileName, ".md") {
continue
}
baseName := strings.TrimSuffix(fileName, ".md")
content := load("posts/" + fileName)
post := &Post{
Slug: baseName,
}
if strings.HasPrefix(content, "---\n") {
end := strings.Index(content[4:], "---\n") + 4
frontmatter := content[4:end]
content = content[end+4:]
parseFrontmatter(frontmatter, func(key, value string) {
switch key {
case "title":
post.Title = value
case "tags":
post.Tags = strings.Split(value, " ")
case "created":
post.Created = value
case "published":
post.Published = value == "true"
}
})
}
post.Content = content
posts[baseName] = post
}
return posts
}
func parseFrontmatter(frontmatter string, assign func(key string, value string)) {
lines := strings.Split(frontmatter, "\n")
for _, line := range lines {
colon := strings.Index(line, ":")
if colon == -1 {
continue
}
assign(line[:colon], strings.TrimSpace(line[colon+1:]))
}
}

66
posts/about.md Normal file
View File

@ -0,0 +1,66 @@
---
title: About
tags: about
created: 2023-07-09T20:04:03Z
published: true
---
Hi 👋
I'm Eduard Urbach, a software developer trying to create high quality [open source](https://git.akyoto.dev/explore/repos) software solutions.
## Philosophy
In an age where technology is meant to simplify our lives, it's ironic how often we find ourselves grappling with bloated software - programs that are swollen with unnecessary features, bogged down by excessive complexity, and burdened with what feels like digital excess baggage.
Welcome to the era of feature creep, where software developers seem to prioritize quantity over quality, overwhelming users with an avalanche of functionalities that they never asked for nor needed.
As a wise man once said:
> Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away.
We shouldn't ask ourselves how much we can add, but how much we can remove, while still maintaining the core task of the software. This leads to much simpler and easy to understand code. I'll quote Terry Davis:
> An idiot admires complexity, a genius admires simplicity.
Making a topic look complex is easy, but making it simple and easy to understand is what truly takes skill. Instead of complex and over-engineered solutions that make us "feel intelligent" just because they're complex we should strive for simplicity.
## Languages
One of the more unique facts about my life is that I spent 8 years in Asia, more specifically Japan and Korea. This helped me broaden the pool of languages I can utilize to communicate and it also gave me an understanding of the subtle differences between Western and Eastern cultures.
Currently I speak:
| | |
| ----------- | -------------------------------- |
| 🇬🇧 English | - |
| 🇩🇪 German | Fußbodenschleifmaschinenverleih. |
| 🇯🇵 Japanese | 仕方ないよね〜 |
| 🇰🇷 Korean | 두유 노 김치? |
| 🇷🇺 Russian | давай давай! |
## Software
I like [Arch Linux](https://archlinux.org) as my operating system for multiple reasons:
- minimal system
- rolling release
- pacman is awesome
- wiki is amazing
My desktop is now based on [Hyprland](https://hyprland.org) after having used [Gnome Shell](https://gnome.org) for the past 10 years.
I use [Neovim](https://neovim.io) as my lightweight editor and [VS Code](https://code.visualstudio.com) only if it has better plugins for the project I'm working on.
I am currently experimenting with [fish](https://fishshell.com) as my daily shell replacing [zsh](https://zsh.org).
## Community
In case you want to contribute to any projects, join [#community:akyoto.dev](https://matrix.to/#/#community:akyoto.dev) with a client you like.
I recommend [Cinny](https://cinny.in/) because it has a beautiful design and closely resembles Discord.
## Donations
- [Kofi](https://ko-fi.com/akyoto)
- [LiberaPay](https://liberapay.com/akyoto/donate)
- [PayPal](https://www.paypal.com/donate/?hosted_button_id=EUBC5TT82APK6)
- [Stripe](https://donate.stripe.com/28o9Dn2xlehb6Zi8ww)

60
posts/blender.md Normal file
View File

@ -0,0 +1,60 @@
---
title: Blender
tags: note troubleshooting
created: 2024-02-05T14:03:13Z
published: true
---
## Basics
| | |
| ---------------- | --------- |
| Add | Shift A |
| Grab | G |
| Rotate | R |
| Scale | S |
| Extrude | E |
| Inset | I |
| Bevel | Ctrl B |
| Loop cut | Ctrl R |
| Fill | F |
| Merge | M |
| Apply | Ctrl A |
| Join | Ctrl J |
| Parent | Ctrl P |
| Grab on X axis | G X |
| Grab on YZ plane | G Shift X |
| Slide | G G |
## Vision
| | |
| -------------- | ------- |
| Hide | H |
| Unhide | Alt H |
| Focus | Num . |
| Focus region | Shift B |
| Focus all | Home |
| Isolate | / |
| Viewpoint menu | ~ |
## Modes
| | |
| ----------- | --- |
| Vertex mode | 1 |
| Edge mode | 2 |
| Face mode | 3 |
## Troubleshooting
### How to rename something?
You can rename objects with F2 but in many other places you need to double click the name with your mouse.
### What is neutral_bone?
When you export a rigged model with Blender in glTF format, you will sometimes notice a `neutral_bone` in the exported data.
This is because some vertices were not assigned a vertex group.
In edit mode, go to `Select > Select all by trait > Ungrouped vertices` to find the problematic vertices and delete them.

17
posts/contact.md Normal file
View File

@ -0,0 +1,17 @@
---
title: Contact
tags: about
created: 2023-07-23T12:43:21Z
published: true
---
| | |
| ------------- | ------------------------------------------------------------ |
| Name | Eduard Urbach |
| Occupation | Software Engineer |
| Business type | Freelancer |
| E-Mail | admin [at] akyoto.dev |
| Chat | [@akyoto:akyoto.dev](https://matrix.to/#/@akyoto:akyoto.dev) |
| Mobile | +49 1520 4296914 |
| Tax number | DE 362234011 |
| Address | [n/a](https://dserver.bundestag.de/btd/19/077/1907714.pdf) |

1912
posts/emoji.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,62 @@
---
title: Environment Variables
tags: article software linux system configuration
created: 2024-03-07T10:09:08Z
published: true
---
Recently, I had to configure some user specific environment variables for my Linux installation.
The problem was that I needed those variables to work in all of my shells and in every desktop environment.
Since `.pam_environment` is deprecated and `.profile` isn't sourced by every shell, I needed another solution that works globally.
## User variables
One solution is to use `systemd` for this task.
You can configure user environment variables in the `~/.config/environment.d` directory.
Multiple files can be added and all of them will be processed on session start:
```
.
├── 10-xdg.conf
├── 20-dirs.conf
├── 30-general.conf
└── 40-apps.conf
```
The format inside these files is very simple:
```ini
KEY=value
KEY=value
KEY=value
```
They support variable expansion so it's possible to use things like `$HOME` in the value text.
You can take a look at my [environment.d](https://git.akyoto.dev/sys/home/src/branch/main/.config/environment.d) if you need some examples.
GDM and KDE Plasma will automatically source these variables on session start.
If you don't use these or you don't want to rely on them, you need to manually load the variables in your shell config files.
### bash / zsh
```bash
export $(/usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator)
```
### fish
```bash
export (/usr/lib/systemd/user-environment-generators/30-systemd-environment-d-generator)
```
Even if you use a graphical environment it makes sense to add these to your shell startup because it allows you to see the changes made to your environment immediately. In case you're worried about execution time, it adds about 3 ms to the shell startup on my machine and is well worth the price.
## System variables
Hardware and system specific variables that do not need to live in a version controlled repository can be set in `/etc/environment`. For example, on a system with an Nvidia GPU:
```ini
GBM_BACKEND=nvidia-drm
LIBVA_DRIVER_NAME=nvidia
__GLX_VENDOR_LIBRARY_NAME=nvidia
```

64
posts/file-structure.md Normal file
View File

@ -0,0 +1,64 @@
---
title: File Structure
tags: article software
created: 2023-07-28T09:21:24Z
published: true
---
There are two widely used types of source code organization and I will explain why one of these should be avoided.
As an example, let's take a look at the model-view-controller pattern:
## "by type"
An inexperienced programmer will often create this file structure when asked to create an MVC project:
```text
.
├── controllers
│   ├── Post.controller
│   └── User.controller
├── models
│   ├── Post.model
│   └── User.model
└── views
├── Post.view
└── User.view
```
We call this "by type" organization and it should be avoided like the devil.
First of all, it violates the software principle that related things should always stay close to each other.
It becomes very hard to find all the files related to a component.
Nowadays fuzzy finders can help offset this problem, but it's nonetheless a problem that could have been avoided in the first place instead of requiring extra tooling.
Secondly, this structure makes deletion non-trivial. If a component is no longer needed, then you need to delete it from your repository.
This should be a trivial task, ideally removing one directory and you're done. If your component, however, happens to be spread out over 15 different folders, then the deletion process is very complex.
## "by feature"
There is a very simple solution to all of these problems and it's called "by feature" organization:
```text
.
├── Post
│   ├── Post.controller
│   ├── Post.model
│   └── Post.view
└── User
├── User.controller
├── User.model
└── User.view
```
With this file structure:
- All related code is closely grouped together
- Deleting a component is very easy
Depending on the style regulations, you might encounter the plural forms `Posts` and `Users`, but it's the same principle. Personally, I prefer singular names because plurals aren't consistent. If your code uses type names via reflection to build the paths, you're better off with singular names.
## Conclusion
- Don't organize files by their file type
- Organize files by feature
- Make deletion simple

19
posts/firefox.md Normal file
View File

@ -0,0 +1,19 @@
---
title: Firefox
tags: note
created: 2024-03-07T18:31:12Z
published: false
---
| | |
| ----------- | -------------- |
| Back | Alt Left |
| Forward | Alt Right |
| Tab left | Ctrl Shift Tab |
| Tab right | Ctrl Tab |
| Address bar | Ctrl L |
| Web search | Ctrl K |
| Quick find | / |
| Zoom in | Ctrl + |
| Zoom out | Ctrl - |
| Zoom reset | Ctrl 0 |

76
posts/git.md Normal file
View File

@ -0,0 +1,76 @@
---
title: Git
tags: note software git
created: 2023-07-01T08:52:17Z
published: true
---
## git
### Delete branch
```bash
git branch -d $branch
```
### Delete remote branch
```bash
git push origin --delete $branch
```
### Push branch to remote
```bash
git push -u origin $branch
```
### Rebase preserving dates
```bash
git rebase -i --root --committer-date-is-author-date
```
### Rename branch
```bash
git branch -m $old $new
```
### Sign last commit
```bash
git commit --amend --no-edit -S
```
### Show log
```bash
git log --oneline
```
### Show remote
```bash
git remote -v
```
### Undo `git add`
```bash
git reset $file
```
## git lfs
### Show files
```bash
git lfs ls-files
```
### Show files in the entire history
```bash
git lfs ls-files --all
```

17
posts/hardware.md Normal file
View File

@ -0,0 +1,17 @@
---
title: Hardware
tags: about
created: 2023-06-28T08:20:41Z
published: true
---
The hardware I'm currently using:
- MSI Z370-A PRO
- Intel Core i7-8700
- Nvidia GeForce GTX 1070
- 32 GB T-Force DDR4 (4 x 8GB)
- 512 GB E5012 M.2 NVMe 2280
- Corsair K70 Rapidfire
- Logitech G Pro Superlight
- BenQ MOBIUZ EX240

26
posts/license.md Normal file
View File

@ -0,0 +1,26 @@
---
title: License
tags: legal license
created: 2023-08-31T13:46:52Z
published: true
---
Copyright © Eduard Urbach
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

192
posts/linux.md Normal file
View File

@ -0,0 +1,192 @@
---
title: Linux
tags: note software linux
created: 2023-07-01T08:51:23Z
published: true
---
The following commands have been tested with Arch Linux.
They might work in other distributions with a few modifications.
The commands assume standard user permissions and will include `sudo` if root permissions are required.
## lsof
### List open TCP connections
```bash
sudo lsof -PniTCP
```
### List open UDP connections
```bash
sudo lsof -PniUDP
```
## nu
### Sort files and directories by size
```bash
ls -ad | sort-by size | reverse
```
### Filter processes with a high cpu usage
```bash
ps | where cpu > 0 | sort-by cpu | reverse
```
### Filter processes with a high memory usage
```bash
ps | where mem > 30MB | sort-by mem | reverse
```
## openssl
### Show certificate info
```bash
openssl x509 -text -noout -in $file
```
## pacman
### Install a package
```bash
sudo pacman -S $package
```
### Uninstall a package and its dependencies
```bash
sudo pacman -Rs $package
```
### List installed packages
```bash
pacman -Q
```
### List orphaned packages
```bash
pacman -Qtdq
```
### Clear cache
```bash
sudo pacman -Sc
```
## rg
### Search recursively
```bash
rg $text
```
### Search single file
```bash
rg $text $file
```
### Disable all filters
```bash
rg -uuu $text
```
## rsync
### Sync directory with a server
```bash
rsync -avz $source $destination
```
### Preview changes
```bash
rsync -avz $source $destination --dry-run
```
## systemd
### List all running services
```bash
systemctl --type=service --state=running
```
### List all enabled services
```bash
systemctl list-unit-files --type=service --state=enabled
```
### Follow log messages
```bash
journalctl -f
```
### Follow log messages for a specific unit
```bash
journalctl -f -u $unit
```
### Show boot time analysis
```bash
systemd-analyze blame
```
### Reboot into BIOS
```bash
systemctl reboot --firmware-setup
```
## tar
### Compress ze vucking files
```bash
tar czvf $name.tar.gz .
```
### Extract ze vucking files
```bash
tar xzvf $name.tar.gz
```
## users
### Add a user
```bash
useradd -m $user
```
### Add a group to a user
```bash
usermod -aG $group $user
```
## wmctrl
### Close all windows
```bash
wmctrl -l | awk '{print $1}' | xargs -rn1 wmctrl -ic
```

59
posts/neovim.md Normal file
View File

@ -0,0 +1,59 @@
---
title: Neovim
tags: note software editor neovim
created: 2023-07-01T10:23:16Z
published: true
---
## Basics
| | |
| ------------------------------- | -------- |
| Normal | `Esc` |
| Insert | `i` |
| Append after cursor | `a` |
| Append at end of line | `A` |
| Append line (after) | `o` |
| Append line (before) | `O` |
| Move to next word | `w` |
| Move to beginning of word | `b` |
| Move to end of word | `e` |
| Move forward to character | `f` |
| Move backward to character | `F` |
| Move to start of file | `gg` |
| Move to end of file | `G` |
| Move to start of line | `0` |
| Move to end of line | `$` |
| Move to first character of line | `^` |
| Select | `v` |
| Select line | `V` |
| Copy | `y` |
| Copy line | `yy` |
| Paste (before cursor) | `P` |
| Paste (after cursor) | `p` |
| Delete | `d` |
| Delete until end of line | `D` |
| Delete line | `dd` |
| Repeat last command | `.` |
| Undo | `u` |
| Redo | `Ctrl r` |
| Search | `/` |
## Commands
| | |
| --------- | ------------- |
| Command | `:` |
| Edit file | `:e file.txt` |
| Replace | `:%s/a/b` |
## Combinations
| | |
| --------------------------------------- | ------ |
| Change word under cursor | `ciw` |
| Delete word and whitespace under cursor | `daw` |
| Rewrite line | `^Da` |
| Select word under cursor | `viw` |
| Select next block | `va{` |
| Select everything | `ggVG` |

View File

@ -0,0 +1,35 @@
---
title: Network Update Rate
tags: article gamedev network multiplayer
created: 2024-02-21T22:17:01Z
published: true
---
In game development, the send rate of server and client updates dictates how good the prediction of the real player position is. However, not all increases in send rate give you equal returns for the increased CPU and bandwidth costs you're paying.
I've recorded and averaged the prediction error over multiple runs on some common send rates to see how much the prediction improves. The test involves a character running in circles which tests a range of parameters like extrapolation, interpolation and update rate.
## Data
| Update rate | Error distance |
| ----------------------------------- | -------------- |
| 1 Hz | 73.4 cm |
| 2 Hz | 51.5 cm |
| 5 Hz | 23.3 cm |
| 10 Hz | 12.5 cm |
| 15 Hz | 8.9 cm |
| 20 Hz | 7.4 cm |
| 25 Hz | 6.2 cm |
| 50 Hz | 4.5 cm |
| 100 Hz | 3.5 cm |
The error distance represents the distance of the client's predicted position to the server position sampled on each network packet receival.
The move speed used in the test was 4.5 meters per second.
The test data shows that there's barely any noticeable benefit in running more than 20 Hz. The movement actually looks very smooth with only 10 Hz, but suffers in a few edge cases (ping-pong movement) where 20 Hz can lead to much better results. The final choice will of course depend on your prediction algorithms and the game type.
## Conclusion
- Too little updates make it really hard to predict where the player is
- Too many updates will severely increase server CPU & bandwidth usage
- Prefer a send & receive rate somewhere in the ballpark of 10-20 Hz

21
posts/projects.md Normal file
View File

@ -0,0 +1,21 @@
---
title: Projects
tags: about
created: 2024-03-03T23:11:14Z
published: true
---
| | |
| --- | --- |
| 👤 [akyoto.dev](https://git.akyoto.dev/web/akyoto.dev) | this website |
| 🚦 [assert](https://git.akyoto.dev/go/assert) | assertions for software correctness |
| 🎮 [bom](https://git.akyoto.dev/game/bom) | game using Godot with Go servers |
| 🌈 [color](https://git.akyoto.dev/go/color) | color for terminals |
| 💿 [data](https://git.akyoto.dev/go/data) | in-memory key value store |
| 🔢 [hash](https://git.akyoto.dev/go/hash) | non-cryptographic hash for etags |
| 🏠 [home](https://git.akyoto.dev/sys/home) | personal dotfiles |
| 📃 [markdown](https://git.akyoto.dev/go/markdown) | markdown renderer for this site |
| 💃 [notify.moe](https://git.akyoto.dev/web/notify.moe) | anime tracker |
| 🌱 [q](https://git.akyoto.dev/cli/q) | simple programming language |
| 🔗 [router](https://git.akyoto.dev/go/router) | http router based on radix trees |
| 🚀 [web](https://git.akyoto.dev/go/web) | web server for this site |

View File

@ -0,0 +1,38 @@
---
title: Separation of Concerns
tags: article software
created: 2018-06-25T00:16:40Z
published: true
---
> Do one thing and do it well.
Separation of concerns is a concept that can be described as the art of only doing what the tool should be concerned with, never more than needs to be done.
You don't expect a knife to function as a lighter. A knife only needs to cut and it does that one thing very well.
## How does this apply to software?
Any type of modern software architecture allows us to structure our code into modules, packages, files, libraries, engines and other types of abstractions that let us reuse existing functionality.
Whenever you write a new module, ask yourself this: Does this module really need to contain all the code it has inside it?
Is every part of this module really dealing with the same problem, the same _concern_?
Chances are high that you have written a module in the past that does much more than it needs to do. The module is doing things that it should not be concerned with. These different concerns should be separated into different modules, each dealing only with the bare minimum they need to deal with.
## What's the benefit?
By doing so, you will notice that your code gains a higher chance of being reused in a different project.
Modules can be tested much easier because the amount of possible states you need to reason about becomes much smaller, therefore contributing to overall better software quality.
## What about frameworks?
This is actually one of the main reasons I don't like monolithic frameworks. You import one package (the framework) and it concerns itself with a multitude of problems. It becomes hard to test and reason about this software. Understanding and fixing bugs becomes difficult because it deals with so many things, the faulty state could have been caused by any part of the system.
Instead, what software developers should strive for, is to make small and independent packages that deal with a single problem only.
Do not offer a single package that deals with all of the world's problems, offer a set of modules that developers can choose from. By doing so, a framework becomes a _specification_ of modules, a _concept_, rather than an actual package with thousand dependencies.
## Conclusion
- Separate concerns.
- Do one thing and do it well.
- Avoid centralism, prefer composition.

View File

@ -0,0 +1,96 @@
---
title: Static IP Configuration
tags: article guide software linux config
created: 2023-07-02T21:43:42Z
published: true
---
When installing Arch Linux on a VPS, you usually need to enable VNC and configure the networking by yourself before you can SSH into your machine.
This post serves as a guide to connect your server to the internet.
## Connectivity check
First of all, confirm that networking hasn't been set up yet:
```bash
ping google.com
```
There should be no response from the ping.
## Network interface
Now let's find out the name of the network interface:
```bash
networkctl list
```
You should see an ethernet device named like `eth0` or `ens0`.
The number at the end will differ.
This is the name of the network interface we'll configure in the following steps.
## Network manager
Arch Linux uses `systemd` which includes the `systemd-networkd` network manager by default.
To configure the network manager, edit the following file:
```bash
sudo vim /etc/systemd/network/20-wired.network
```
Start by specifying the network interface name:
```ini
[Match]
Name=ens0
```
Now you need to enter your IP address, subnet mask and the gateway.
Ideally your VPS provider should include that data in your dashboard.
You can enter the addresses in the network section:
```ini
[Network]
Address=2.1.1.2/32
Gateway=2.1.1.1
DNS=1.1.1.1
```
The DNS shown here is the Cloudflare DNS because it's reliable and easy to remember,
but feel free to use a different DNS.
If your VPS has an IPv6 address you can add more of the same lines to the network section:
```ini
Address=1111:1111:1111::2345
Gateway=1111:1111:1111::1111
DNS=2606:4700:4700::1111
```
## Service installation
Try to start the DNS resolver and the network manager:
```bash
sudo systemctl start systemd-resolved
sudo systemctl start systemd-networkd
```
Check if we're online on both IPv4 and IPv6:
```bash
ping -4 google.com
ping -6 google.com
```
If everything went smooth, make the DNS resolver and the network manager start automatically on boot:
```bash
sudo systemctl enable systemd-resolved
sudo systemctl enable systemd-networkd
```
## DNS records
To finalize the setup, add an `A` record for IPv4 and an `AAAA` record for IPv6 to your DNS and your server should be fully configured.

65
posts/tabs-vs-spaces.md Normal file
View File

@ -0,0 +1,65 @@
---
title: Tabs vs. Spaces
tags: article software
created: 2018-06-24T07:03:20Z
published: true
---
Let's take a look at tabs vs. spaces. This discussion is famous amongst all skill levels of developers and it seems that noone can really convince the other side. People have different opinions on why one side is better than the other and cannot unanimously agree on one style. I think one of the main reasons for this problem is that the discussion is always about 100% tabs vs. 100% spaces instead of looking at an alternative approach: Using both.
## Both?
At first it might sound weird, but we're not just going to randomly insert a different indentation character whenever we want to. There is going to be a ruleset and that ruleset will be well defined. That means by going hybrid, there are no discussions needed for when tabs or spaces should be used, as every situation will be clearly covered by those rules.
## Which rules, exactly?
1. Tabs for semantic indentation
2. Spaces for presentational alignment
### Semantic indentation
> Relating to meaning in language or logic.
Semantic indentation is required when the whitespace has a meaning in the given context. Note that _meaning_ doesn't necessarily imply that it is needed for the compiler to process the code as in the case of Python e.g., _meaning_ can simply refer to an indented block of code, let's say a `for` loop, where the body of the loop is semantically different from the surrounding code and therefore is indented.
```go
for {
// This is semantic indentation: Use tabs.
}
```
Semantic indentation should be performed as the very first step, using **tabs**.
Spaces would need to be repeated multiple times to represent a single meaning. This is inefficient for disk space and therefore code parsing, let alone the fact that doing so doesn't give us any benefits in the first place. Or when was the last time you used 4 square brackets to represent an array in JSON?
```json
[[[["Behold the new JSON standard!"]]]]
```
Why use more when you can do it with less?
We also get the added benefit of letting every team member be able to adjust the tab width and therefore personalize how much pixel space he or she would prefer to be used for a single tab. This can heavily improve readability for a person that is used to very different widths on his or her monitor. Normally, if you used tabs 100% of the time, this argument would be held up **against** using tabs because it can break the alignment of your code. That is exactly why we are going to define separate rules for alignment.
### Presentational alignment
> The proper positioning or state of adjustment of parts in relation to each other.
Presentational alignment refers to positioning different parts better in relation to each other and does not convey any meaning. It will make the code look better but the whitespace doesn't convey that the code is different from the surrounding code, thus being only used to align parts with each other for presentational purposes.
```go
type User struct {
// Note how we're using tabs to indent the struct
// and spaces to align the name of the data type.
ID string
Name string
Age int
}
```
Alignment should be used _after_ semantic indentation and should be done using **spaces**. The reason is that alignment requires precise positioning on a monospace font and the tab character can't do that because it has dynamic width.
Your team members will thank you because they can finally change the indentation width to something they like and it won't break the alignment of the code.
## Conclusion
Rather than going full tabs or full spaces, consider using a hybrid approach where each character does what it's best at and therefore combining the best of both worlds. I have been using this system for as long as I can remember and I never had a problem with it. For me, this is the de facto standard nowadays and the more people start switching to this system, the better. Amen.

View File

@ -0,0 +1,98 @@
---
title: The Final Newline
tags: article software
created: 2023-06-28T14:52:08Z
published: true
---
The beautiful thing about the language of mathematics is that it is precise and definitions must apply to every case
presented, 100%, else the definition is considered wrong and needs to be altered. 99% isn't good enough for a
definition. If there is even a single case showing that the proposed definition doesn't apply to it, we consider the definition to be incorrect.
I'm a little surprised that in software engineering, which must be as precise as mathematics in definitions and specifications, we have come to accept this definition of a line:
> A sequence of zero or more non-newline characters plus a terminating newline character.
As mentioned earlier, if there is even one case that doesn't satisfy the definition, then it's incorrect.
This definition of a line completely falls apart when you look at a file with the following contents:
```text
This is a line of text.
```
Let's save this as `single-line.txt`, without the newline character at the end.
Is this a line? Of course it is, humans don't use line terminators when writing something down in real life.
Ask any person around you, they will say "yeah, it's one line of text".
## Counting lines
Without a final newline character, POSIX compliant programs will fail to recognize this line:
```bash
> "This is a line of text." | wc -l
0
```
That's because the POSIX definition of a line is wrong. We have one line of text and the computer is telling us we have zero lines in our file.
Now, before you go and send pull requests to the authors of `wc`, let me quickly add: Their documentation explicitly mentions that the flag abbreviated with `-l` does - in fact - not count lines. 🤔
It only counts _newlines_. The authors of `wc`, Paul Rubin and David MacKenzie, probably wanted to avoid this
controversy and the documentation states that it only counts newline characters, not lines.
## Separator vs. Terminator
There are 2 ways to interpret the newline character:
- As a line separator
- As a line terminator
With the "line separator" interpretation you basically treat the contents of a file as a
```go
strings.Join(lines, "\n")
```
whereas the "line terminator" interpretation is better expressed as:
```go
for _, line := range lines {
w.WriteString(line)
w.WriteByte('\n')
}
```
## Definition vs. Regulation
As we have seen, not every line is terminated with a newline character.
Thus we cannot regard the "line terminator" interpretation as a _definition_, because it's incorrect.
It's a _regulation_.
A regulation is different because it forces you to do something as opposed to trying to define something.
## Does the POSIX regulation make sense?
The thing about regulations is that we all hate them, but we hate inconsistencies even more.
Even if I personally think that the newline character should be a line separator because it's more in line (pun intended) with how humans see a text file,
I also recognize that some fights aren't worth fighting.
It would have been a lot easier to discuss this topic in the early days of computing, before the existence of tools that adopted the POSIX standard.
Now it's hard to argue against it when your co-worker just wants an easy way to disable the annoying "No newline at end of file" warning on every `git diff`.
So my suggestion to developers is that everybody needs to decide for themselves what is worth their time.
If you say that you want to argue with existing standards and try to improve them for the future, then I will fully
respect that. But know what you're getting yourself into. Everything has an [opportunity cost](https://en.wikipedia.org/wiki/Opportunity_cost).
I think it's mostly the younger generation that tries to initiate changes and that's not necessarily a bad thing.
Deep down, I really wish somebody would stop this line terminating madness someday. But this isn't my fight.
## Conclusion
- In an ideal world, newline characters would be line separators, not line terminators
- Tools in the UNIX world have already widely adopted the POSIX regulation
- It's very hard to make a change in this area
- Decide for yourself if this is a fight that is worth your time [or not](https://en.wikipedia.org/wiki/Law_of_triviality)
- If you are not the owner, adopt the style regulations of the project you are contributing to

102
posts/vector.md Normal file
View File

@ -0,0 +1,102 @@
---
title: Vector
tags: note vector math
created: 2023-08-29T11:59:28Z
published: true
---
## 2D
### Angle
```go
atan2(y, x)
```
### Cross
```go
x * y2 - y * x2
```
### Distance
```go
(a - b).Length()
```
### Dot
```go
x * x2 + y * y2
```
### From angle
```go
x = cos(angle)
y = sin(angle)
```
### Length
```go
sqrt(x * x + y * y)
```
### Normalize
```go
length := x * x + y * y
if length == 0 {
return
}
length = sqrt(length)
x /= length
y /= length
```
## 3D
### Cross
```go
( y * z2 - z * y2,
z * x2 - x * z2,
x * y2 - y * x2 )
```
### Distance
```go
(a - b).Length()
```
### Dot
```go
x * x2 + y * y2 + z * z2
```
### Length
```go
sqrt(x * x + y * y + z * z)
```
### Normalize
```go
length := x * x + y * y + z * z
if length == 0 {
return
}
length = sqrt(length)
x /= length
y /= length
z /= length
```

46
posts/workspace-matrix.md Normal file
View File

@ -0,0 +1,46 @@
---
title: Workspace Matrix
tags: article software linux productivity
created: 2024-03-01T21:57:39Z
published: true
---
I use a 3 x 3 workspace matrix with the numbers on my numpad bound to each workspace.
My wife also started using the same workflow and she has been pretty fond of it ever since.
I wanted to share this method in case anyone else wants to try this on GNOME or Hyprland.
For KDE users: I believe the settings menu lets you configure this out of the box.
## Gnome
Normally, you can only set 4 workspace shortcuts in the GNOME settings menu.
However, by accessing the dconf settings you can circumvent this restriction.
```bash
export WS=/org/gnome/desktop/wm/keybindings/switch-to-workspace
dconf write $WS-1 "['KP_7']"
dconf write $WS-2 "['KP_8']"
dconf write $WS-3 "['KP_9']"
dconf write $WS-4 "['KP_4']"
dconf write $WS-5 "['KP_5']"
dconf write $WS-6 "['KP_6']"
dconf write $WS-7 "['KP_1']"
dconf write $WS-8 "['KP_2']"
dconf write $WS-9 "['KP_3']"
```
## Hyprland
Simply add this to your config file.
```ini
bind = , KP_Home, workspace, 1
bind = , KP_Up, workspace, 2
bind = , KP_Prior, workspace, 3
bind = , KP_Left, workspace, 4
bind = , KP_Begin, workspace, 5
bind = , KP_Right, workspace, 6
bind = , KP_End, workspace, 7
bind = , KP_Down, workspace, 8
bind = , KP_Next, workspace, 9
```

View File

@ -100,6 +100,7 @@ blockquote p::after {
h1 {
color: white;
font-size: 2.2rem;
font-weight: normal;
letter-spacing: -0.02em;
}
@ -137,28 +138,14 @@ th:empty {
display: none;
}
@media (min-width: 800px) {
h2 {
margin-left: -1rem;
}
.post-header {
margin-left: -1rem;
}
.post-time {
margin-left: 0.5rem;
}
}
header,
body > header,
main {
width: 100%;
max-width: var(--max-width);
padding: var(--padding);
}
header {
body > header {
max-width: calc(var(--max-width) + 4rem);
}
@ -186,11 +173,21 @@ nav a:hover {
color: var(--main-color);
}
p time {
article header time {
font-size: 0.8rem;
color: var(--grey-color);
}
.blog li {
display: flex;
}
.blog li time {
flex: 1;
text-align: right;
color: var(--grey-color);
}
.comment {
color: gray;
font-style: italic;
@ -238,11 +235,12 @@ p time {
}
@media (min-width: 800px) {
article header,
h2 {
margin-left: -1rem;
}
p time {
margin-left: .5rem;
article header time {
margin-left: 0.5rem;
}
}