parent
3a8dd659dd
commit
02f7e7a1a0
@ -0,0 +1,7 @@
|
||||
((nil . ((eval . (add-hook 'after-save-hook
|
||||
(lambda ()
|
||||
(when (string= (file-name-extension (buffer-file-name)) "org")
|
||||
(setq default-directory (file-name-directory (locate-dominating-file "." ".dir-locals.el")))
|
||||
(load (concat default-directory "build.el"))
|
||||
nil
|
||||
t)))))))
|
@ -0,0 +1,91 @@
|
||||
;; Only for running rebuild from within Emacs
|
||||
(setq original-buffer (current-buffer))
|
||||
|
||||
;; Set working directory to the directory of this script
|
||||
(setq working-directory (file-name-directory load-file-name))
|
||||
(setq default-directory working-directory)
|
||||
|
||||
;; Set ~/.emacs.d/elpa path. ./.packages is relative to default-directory
|
||||
(require 'package)
|
||||
(setq package-user-dir (expand-file-name "./.packages"))
|
||||
(setq package-archives '(("melpa" . "https://melpa.org/packages/")
|
||||
("elpa" . "https://elpa.gnu.org/packages/")))
|
||||
|
||||
;; Initialize the package system
|
||||
(package-initialize)
|
||||
(unless package-archive-contents
|
||||
(package-refresh-contents))
|
||||
|
||||
;; Install dependencies
|
||||
(package-install 'ox-hugo)
|
||||
|
||||
;; Set Hugo directory
|
||||
;; we can't use the content directory here because we'll need to rename files
|
||||
;; When running hugo server, this would cause unnecessary rebuilds
|
||||
(setq org-hugo-base-dir "/tmp/ox-hugo/")
|
||||
|
||||
;; Make sure ~key-binding~ is rendered as <kbd>key-binding</kbd> not `key-binding`
|
||||
(setq org-hugo-use-code-for-kbd t)
|
||||
|
||||
;; Create required static folder
|
||||
(make-directory (concat org-hugo-base-dir "static") :parents)
|
||||
|
||||
;; Delete old markdown files
|
||||
(dolist (file (directory-files-recursively "content" "\\.md$"))
|
||||
(unless (file-exists-p (concat (file-name-sans-extension file) ".org"))
|
||||
(delete-file file)))
|
||||
|
||||
;; Generate
|
||||
(dolist (file (directory-files-recursively "content" "\\.org$"))
|
||||
;; Open file
|
||||
(find-file file)
|
||||
|
||||
;; Convert to markdown
|
||||
(org-hugo-export-wim-to-md)
|
||||
|
||||
;; Store markdown file name: path/to/file.org -> file.md
|
||||
(setq file-name (concat (file-name-sans-extension (file-name-nondirectory file)) ".md"))
|
||||
|
||||
;; Reset default-directory, it becomes the parent of the file
|
||||
(setq default-directory working-directory)
|
||||
|
||||
;; Target markdown file
|
||||
(setq target (concat (file-name-directory file) file-name))
|
||||
|
||||
;; Check if creating new file
|
||||
(setq new (not (file-exists-p target)))
|
||||
|
||||
;; Make file writable
|
||||
(unless new (set-file-modes target #o644))
|
||||
|
||||
;; Open temporary buffer
|
||||
(with-temp-buffer
|
||||
;; Load generated markdown
|
||||
(insert-file-contents (concat org-hugo-base-dir "content/posts/" file-name))
|
||||
|
||||
;; Fix image paths: /ox-hugo/image.png -> image.png
|
||||
(while (re-search-forward "/ox-hugo/\\(.*\\)" nil t)
|
||||
(replace-match "\\1"))
|
||||
|
||||
;; Fix image formatting
|
||||
;; Write to target unless generated markdown matches old content
|
||||
;; It would be simpler to run file-equal-p,
|
||||
;; but since we already have one of the files loaded that would be inefficient
|
||||
(unless (and (not new) (equal (buffer-string) (with-temp-buffer
|
||||
(insert-file-contents target)
|
||||
(buffer-string))))
|
||||
(write-region (point-min) (point-max) target nil)
|
||||
))
|
||||
|
||||
;; Make file read-only
|
||||
(set-file-modes target #o444)
|
||||
|
||||
;; Close file unless original buffer
|
||||
(unless (eq (current-buffer) original-buffer)
|
||||
(kill-buffer)))
|
||||
|
||||
;; Clean up temporary directory
|
||||
(delete-directory org-hugo-base-dir t)
|
||||
|
||||
;; Switch back to original buffer
|
||||
(switch-to-buffer original-buffer)
|
@ -1,4 +1,5 @@
|
||||
#!/bin/bash
|
||||
emacs -Q --script build.el
|
||||
hugo
|
||||
./stork_index.sh
|
||||
stork build --input "public/stork.config.json" --output "public/home.st"
|
||||
find public -name '*.html' -type f -exec tidy --quiet yes --drop-empty-elements no --hide-comments yes -mq '{}' \; &> /dev/null
|
||||
|
@ -0,0 +1,17 @@
|
||||
#+TITLE: Elnu's Blog
|
||||
|
||||
#+begin_quote
|
||||
年暮ぬ笠きて草鞋はきながら
|
||||
#+end_quote
|
||||
|
||||
Hello! I go by Elnu on the internet, and this is my blog, I hope you find something of interest here. I'm 17, I'm interested in programming, GNU/Linux, studying Japanese, watching anime, drawing, and creative writing. I'm horrible at doing things consistently.
|
||||
|
||||
For now, I'll be posting small Linux and programming-related posts explaining how to do various things that I happen to find useful. In the future, I'll post larger, more interesting posts. Stay tuned! (〃^▽^〃)
|
||||
|
||||
* Quick links
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: quick-links
|
||||
:END:
|
||||
:right_arrow: :books: [[/posts][Post archive]]\\
|
||||
:right_arrow: :flags: [[/tags/nihongo][#nihongo]] Japanese writing\\
|
||||
:right_arrow: :floppy_disk: [[/tags/programming][#programming]] Programming
|
@ -0,0 +1,124 @@
|
||||
#+TITLE: Creating a Firefox PWA for Discord on Linux
|
||||
#+DATE: 2022-07-04
|
||||
#+FILETAGS: programming
|
||||
|
||||
In this tutorial, I'll explain how to set up, install, and configure a Firefox [[https://en.wikipedia.org/wiki/Progressive_web_application][PWA]] (Progressive Web Application) for Discord that integrates into your system, and some background on why you'd want to do so over the regular desktop version. I've tested these steps on Arch Linux and Xubuntu 20.04.
|
||||
|
||||
* Background
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: background
|
||||
:END:
|
||||
If you've ever used Discord on Linux, you probably know that it is clunky and not nearly as polished or feature-complete as it is on, say, Windows.
|
||||
|
||||
Streaming has no audio, and while using [[https://soundux.rocks/][Soundux]] as a workaround for audio passthrough is a solution, it can be annoying to use, and doesn't always work. For example, in my experience, Soundux fails to pick up audio from Java Minecraft (and I believe by extension all Java applications). In addition, I've recently had issues with audio output, with no sound being produced other than voices in VC.
|
||||
|
||||
Another major issue with desktop Discord is the fact it isn't sandboxed at all, meaning it has full access to your system at any time. In addition, if you're desktop is using X.org, Discord (and any other application, for that matter) is able to snoop on all key inputs, even if they're from applications. Make of this what you will.
|
||||
|
||||
Not to mention the issues with the updater, which has been excellently covered in [[https://www.youtube.com/watch?v=3OEfr7L-gUk][this video by Brodie Robertson]].
|
||||
|
||||
There is of course a second way to use Discord on Linux, which is simply the web application. It does everything that the desktop application does, including streaming (albeit without audio), and it doesn't ever require being updated. The only potential downside is the lack of push-to-talk, but if like me this is something you don't use, then this isn't an issue. In addition, since it's just a web page not a fully-fledged application it doesn't have the security/privacy concern of being able to access your entire system like the desktop version.
|
||||
|
||||
The only frustration I have with the web version is the fact that it doesn't look like a standalone application on your desktop, it's just a tab in your browser. Being able to remove as much of the top bar as possible would be nice. This is where PWAs come into play.
|
||||
|
||||
Unfortunately, support for SSB (Site Specific Browser) mode and PWAs was [[https://bugzilla.mozilla.org/show_bug.cgi?id=1682593][dropped]] in Firefox 86 last year.
|
||||
|
||||
Luckily, *Progressive Web Apps for Firefox* ([[https://github.com/filips123/PWAsForFirefox][filips123/PWAsForFirefox]]) exists to fill the lapse in functionality.
|
||||
|
||||
#+begin_quote
|
||||
This project creates a custom modified Firefox runtime to allow websites to be installed as standalone apps and provides a console tool and browser extension to install, manage and use them.
|
||||
|
||||
#+end_quote
|
||||
|
||||
* Installation
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: installation
|
||||
:END:
|
||||
First, install the [[https://addons.mozilla.org/en-US/firefox/addon/pwas-for-firefox/][PWAs for Firefox extension from the Firefox add-ons store]]. Once you open it up, it will guide you through the installation process, including installing the companion application. If you're on Arch Linux, it's available on the AUR under =firefox-pwa= and =firefox-pwa-bin=. (=bin= for the binary precompiled version, go for this one if you don't want to compile it yourself.)
|
||||
|
||||
*If you are on Ubuntu or one of its derivatives,* the snap version of Firefox that came with your system will not be able to communicate with the companion application, I'm assuming due to snaps' containerization, at least in my testing on Xubuntu 22.04. See [[https://askubuntu.com/a/1404401][this Stack Overflow answer]] for a guide on switching to the standard Debian package version of Firefox.
|
||||
|
||||
* Create the Discord PWA
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: create-the-discord-pwa
|
||||
:END:
|
||||
Navigate to Discord [[https://discord.com/channels/@me][https://discord.com]] (*not the app*, we need the root of the PWA to be the index page so all pages under the Discord domain are considered part of the PWA) and click on /Install current site/ in the extension. Change the /Name/ field of the PWA to just "Discord", the default will include the tagline and be a bit overly long. Change the /Start URL/ to [[https://discord.com/app]] so the PWA opens to the app page, not the landing page. And... you're done! You can leave all the other fields as-is and press /Install web app/. You've created a PWA for Firefox, it's as simple as that. You can now open it from within the extension by clicking on the external link button.
|
||||
|
||||
However, there's more configuration to be done. Firefox PWA creates a Firefox profile for the PWAs that's completely separate from your main profile, so we're going to have to do some configuration there. (By default, all of your PWAs share a single /Default/ profile under the /Profiles/ tab of the extension, but if you want to have different PWAs have different profiles you can configure it there.)
|
||||
|
||||
* Configure the PWA Firefox profile
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: configure-the-pwa-firefox-profile
|
||||
:END:
|
||||
It's up to you how want to configure the profile, but here's what I did and recommend.
|
||||
|
||||
- In the :gear: /General/ portion, there's a new settings section, /Progressive Web Apps/. For me I checked /Open out of scope URLs in a default browser,/ which makes it so non-Discord links you visit within the PWA profile are opened in your main Firefox profile instead of within the PWA. This is also important considering any external sites you visit within the PWA aren't otherwise going to be added to your main profile's history.
|
||||
- Under :jigsaw: /Extensions & Themes/, you You might also want to [[https://addons.mozilla.org/en-US/firefox/addon/discord-dark-rebrand/][choose a theme that matches Discord's theming]].
|
||||
- Under :lock: /Privacy & Security/ / /History/, set that history to /Use custom settings/ for history and uncheck all the boxes. This will make it so the profile won't keep any history (which is unnecessary in a PWA), but still will remember cookies so you don't have to log in again each time you reopen it.
|
||||
|
||||
If you shut down your computer with the PWA open, Firefox will think that your previous session didn't close properly and will reopen your old windows. I haven't figured out a way to prevent this, but if you want to mess with advanced configuration, here's how.
|
||||
|
||||
Within the PWA, we don't have access to the URL bar, so to enter the preferences, press Ctrl + Shift + I to enter the developer tools. Enter the console and type in the following:
|
||||
|
||||
#+begin_src js
|
||||
document.location.href = "about:config"
|
||||
#+end_src
|
||||
|
||||
Firefox will make you type =allow pasting= beforehand.
|
||||
|
||||
* Launching the PWA from the command line
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: launching-the-pwa-from-the-command-line
|
||||
:END:
|
||||
*If you are using a distro with a desktop environment,* Discord should already be in your applications drawer, so unless you want to be able to launch Discord from the command line, you can skip this step.
|
||||
|
||||
I have uninstalled the desktop Discord application =discord=, and I want to replace it with a shell script that launches the PWA. This will enable you to launch the PWA from the command line simply by typing =discord=, and also from an application launcher like [[https://github.com/davatorium/rofi][rofi]]. This way, we don't have to rely on launching Discord from the PWAsForFirefox extension.
|
||||
|
||||
Luckily, PWAs for Firefox provides a CLI. First, we need to find the ID of the Discord PWA:
|
||||
|
||||
#+begin_src sh
|
||||
firefoxpwa profile list
|
||||
#+end_src
|
||||
|
||||
This will output the following. After the entry for /Discord/, the string of letters and numbers is the site ID. Your ID will be different.
|
||||
|
||||
#+begin_example
|
||||
========================= Default ==========================
|
||||
Description: Default profile for all sites
|
||||
ID: 00000000000000000000000000
|
||||
|
||||
Sites:
|
||||
- Discord: https://discord.com/ (01G705MTCT1Q1HHFM7DVDFAPCY)
|
||||
#+end_example
|
||||
|
||||
You can now launch the PWA using the following command.
|
||||
|
||||
#+begin_src sh
|
||||
firefoxpwa site launch 01G705MTCT1Q1HHFM7DVDFAPCY
|
||||
#+end_src
|
||||
|
||||
Let's create the launch script. Open =/usr/bin/discord= (if this exists already, uninstall the desktop Discord application) with your favorite text editor. You'll need to use =sudo=. Add the following to the file, replacing the site ID with the one you found earlier:
|
||||
|
||||
#+begin_src sh
|
||||
#!/usr/bin/env bash
|
||||
firefoxpwa site launch 01G705MTCT1Q1HHFM7DVDFAPCY
|
||||
#+end_src
|
||||
|
||||
Finally, make it an executable:
|
||||
|
||||
#+begin_src sh
|
||||
sudo chmod +x /usr/bin/discord
|
||||
#+end_src
|
||||
|
||||
You're done! You've created a PWA for Discord that looks and behaves like a desktop application, but without a lot of the hassle involved with the actual desktop app.
|
||||
|
||||
Note that if you decide to reinstall the normal Electron-based desktop Discord application, you'll have to move/delete =/usr/bin/discord= first, as desktop Discord needs to put its executable there.
|
||||
|
||||
* Conclusion
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: conclusion
|
||||
:END:
|
||||
Of course, this isn't a perfect solution. Dragging and dropping files into the PWA doesn't work for uploading, there might be some improved voice chat performance in the normal desktop version not available in the browser/PWA version, push-to-talk doesn't work, and streaming still has no audio.
|
||||
|
||||
However, if you've already been using primarily the browser version of Discord, creating a PWA is a nice upgrade until Discord finally makes a good client. Here's to hoping that day actually comes.
|
||||
|
||||
Anyhow, I hope this was helpful!
|
@ -0,0 +1,84 @@
|
||||
#+TITLE: Handling Multiple Possible Errors in Rust
|
||||
#+DATE: 2022-08-04
|
||||
#+FILETAGS: programming
|
||||
#+HUGO_DRAFT: t
|
||||
|
||||
* Background
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: background
|
||||
:END:
|
||||
Rust handles errors better than any other language I've ever used. All possible errors are known at build time, and by design Rust forces you to at a bare minimum /acknowledge/ that these errors are possible with =.unwrap()=, otherwise writing specific error handling logic.
|
||||
|
||||
** The =Result= enum
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: the-result-enum
|
||||
:END:
|
||||
All of this is thanks to the =Result= enum, which is defined as follows:
|
||||
|
||||
#+begin_src rust
|
||||
enum Result<T, E> {
|
||||
Ok(T),
|
||||
Err(E),
|
||||
}
|
||||
#+end_src
|
||||
|
||||
=Result= is a generic type that requires two type parameters, =T=, the actual value the result is wrapping, and =E=, the error type in case something goes wrong. In the success case, the =Ok(T)= variant will be used, and in an error case, =Err(E)= will be used.
|
||||
|
||||
What makes this powerful is the fact that since =Result= wraps the actual result value, access to that value is prevented unless the possibility of an error is acknowledged.
|
||||
|
||||
** Handling =Result=
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: handling-result
|
||||
:END:
|
||||
For the sake of example, say we're writing a theoretical function, =acquire_banana=, that drives to the store, purchases a banana, and returns the banana.
|
||||
|
||||
We have a function called =drive_to_store=. It returns a =Result= that can either be nothing =()=, or a =NavigationError=.
|
||||
|
||||
#+begin_src rust
|
||||
fn drive_to_store() -> Result<(), NavigationError> { ... }
|
||||
#+end_src
|
||||
|
||||
We then have the following function, =acquire_banana=. It also returns a =Result=, but this time it can either be a =Banana= or a =PurchaseError=.
|
||||
|
||||
#+begin_src rust
|
||||
fn purchase_banana() -> Result<Banana, PurchaseError> { ... }
|
||||
#+end_src
|
||||
|
||||
In other languages, we might write the function something like this:
|
||||
|
||||
#+begin_src rust
|
||||
fn acquire_banana() -> Banana {
|
||||
drive_to_store();
|
||||
purchase_banana()
|
||||
}
|
||||
#+end_src
|
||||
|
||||
However, this doesn't work in Rust. There are two issues here. First, while =drive_to_store();= is in theory valid code (it returns an enum that we ignore), =Result=s *must* be handled. Otherwise, you will get compile warnings. Second, we cannot return =purchase_banana= from the function, because its return type is =Result<Banana, PurchaseError>=, not =Banana=.
|
||||
|
||||
The naïve solution to this would be using the =unwrap= method on the =Result=s, like this:
|
||||
|
||||
#+begin_src rust
|
||||
fn acquire_banana() -> Banana {
|
||||
drive_to_store().unwrap();
|
||||
purchase_banana().unwrap()
|
||||
}
|
||||
#+end_src
|
||||
|
||||
The =.unwrap()= method does exactly what it sounds like. If a =Result= is the =Ok(T)= variant, it returns =T=. However, the naïvety here lies in what happens on the =Err(E)= variant. In that case, it /panics/, printing the error and kills the thread/program. A well-designed program should take errors into account, so this solution is unacceptable.
|
||||
|
||||
The best solution is in this case is *error propagation* using the question mark =?= operator. It works similarly to =.unwrap()=, but instead of panicking on the =Err(E)= case, it returns =Err(E)= from the function. This lets you quickly unwrap results without convoluted =match= statements.
|
||||
|
||||
This lets you convert this:
|
||||
|
||||
#+begin_src rust
|
||||
let x = match get_result() {
|
||||
Ok(value) => value,
|
||||
Err(error) => return Err(error),
|
||||
};
|
||||
#+end_src
|
||||
|
||||
Into this:
|
||||
|
||||
#+begin_src rust
|
||||
let x = get_result()?;
|
||||
#+end_src
|
@ -0,0 +1,12 @@
|
||||
#+TITLE: Managing Rendering of LaTeX
|
||||
#+DATE: 2022-10-25
|
||||
#+FILETAGS: programming
|
||||
|
||||
Previously I've used =pdflatex= to render my LaTeX documents, but I've just come across =latexmk=, which provides much more powerful options. Here's a list of the commands I make use of, taken from [[https://mg.readthedocs.io/latexmk.html][this guide]] by Matthias Geier (mgeier).
|
||||
|
||||
- =latexmk -pdf [file]= Generate a PDF file from a TeX file. The =-pdf= option prevents the additional generation of [[https://en.wikipedia.org/wiki/Device_independent_file_format][DVI files]], the machine-readable version of TeX. Omitting the file name will generate all of the files in the current directory.
|
||||
- =latexmk -c [file]= Delete all extra temporary files created in the rendering process.
|
||||
- =latexmk -C [file]= Delete all generated files, only leaving the original TeX files. (Clean directory.)
|
||||
- =latexmk -pvc <file>= (File parameter only optional if there is only one TeX file in the directory.) Open up a previewer that automatically refreshes as you are editing your file! Previously, I was using the [[https://github.com/xuhdev/vim-latex-live-preview#usage][xuhdev/vim-latex-live-preview]] extension for Vim using the =:LLPStartPreview= command, but it was randomly =Failed to compile= errors, so it made debugging your markup difficult. By default (at least on my system), =-pvc= opens up the xdvik previewer. You can change this by [[https://mg.readthedocs.io/latexmk.html#configuration-files][updating =~/.latexmkrc=]].
|
||||
|
||||
Also a side note, when writing this up I came across [[http://docopt.org/][docopt]], a standard for writing CLI documentation. I had a general idea of the syntax already from seeing it used all over the place, but knowing that there's a standardized spec to refer to is nice. For example, I didn't realize until now that square brackets =[]= are used when arguments are optional, rather than the standard =<>=. The more you know.
|
@ -0,0 +1,11 @@
|
||||
#+TITLE: Soudan: a Comment System Built With Rust!
|
||||
#+DATE: 2022-07-24
|
||||
#+FILETAGS: programming
|
||||
|
||||
I just finished working on Soudan, a Rust-based comment system!
|
||||
|
||||
It's now live on all posts on this site, feel free to give it a go in the comments at the bottom of the page! Markdown formatting is supported. If you submit your email address, it won't be public, and is only used to display your [[https://gravatar.com/][Gravatar]], if you have one.
|
||||
|
||||
Soudan is named after the word 相談 in Japanese, which means /discussion/ or /consultation/.
|
||||
|
||||
The source code is available on +GitHub [[https://github.com/ElnuDev/soudan][here]]+ my [[https://gogs.io/][Gogs]] instance [[https://git.elnu.com/ElnuDev/soudan][here]] under the GNU General Public License v3.0.
|
@ -0,0 +1,57 @@
|
||||
#+TITLE: Tech Maintenance
|
||||
#+DATE: 2022-10-02
|
||||
#+FILETAGS: programming
|
||||
|
||||
/[[https://github.com/cat-milk/Anime-Girls-Holding-Programming-Books/blob/master/Linux/Sakurajima_Mai_Linux_Essentials.jpg][Cover photo]] courtesy of [[https://github.com/cat-milk/Anime-Girls-Holding-Programming-Books][cat-milk/Anime-Girls-Holding-Programming-Books]]./
|
||||
|
||||
*This post is going to be a bit different than the usual programming posts. It's more for myself to organize my thoughts on things that need to be done.*
|
||||
|
||||
Overall, there are quite a few technological things that require maintenance. For a long time I've just been doing what's easiest and what works, and now the side effects of that are piling up. Things I've put off doing, etc. So here's a list of what needs to be maintained, in no particular order.
|
||||
|
||||
I'm probably forgetting something, but this all I can think of for now. I'll be checking off and amending to this list with dates as I go along in the future.
|
||||
|
||||
* Having a clear, fresh start with the server.
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: having-a-clear-fresh-start-with-the-server.
|
||||
:END:
|
||||
Currently, the server is running Ubuntu Desktop and still keeps quite a few unneeded GUI applications, which require updating. In addition, my desktop Linux distribution is now Arch Linux, so for having a more streamlined set up on desktop & server it'd be best to put Arch Linux on it. (However, [[Look into NixOS's viability as an Arch alternative, both for desktop and server use.][here]].) Not to mention how much of a pain Debian package repositories are: I need to install brew on the server just to be able to get the latest version of Hugo.
|
||||
|
||||
* Backups.
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: backups.
|
||||
:END:
|
||||
I need to have a solid, reliable backup system for both my server files and desktop files. Ideally, this would comprise of a secondary drive on the server on which periodic updates can be run (perhaps with rsync?) both from desktop and from the server. This drive should ideally by a terabyte, but something smaller could do in the meanwhile.
|
||||
|
||||
* Beginning the transition away from GitHub.
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: beginning-the-transition-away-from-github.
|
||||
:END:
|
||||
For a variety of reasons already explained [[https://sfconservancy.org/GiveUpGitHub/][here on the Software Freedom Conservancy's website]], it's time to move away from GitHub. Git was originally intended to be a free and distributed version control system, but the prevalence of the sentiment "Git = GitHub" shows how Microsoft has exploited the FOSS community into becoming dependent on a completely proprietary platform, using its unfettered access to the code of developers around the world to its benefit by training GitHub Copilot on that data without consent. The obvious easy solution is simply setting up headless Git repositories with SSH access on one's own server as described [[https://www.youtube.com/watch?v=ju9loeXNVW0][here]] by Luke Smith. However, this only covers the easy backup aspect of GitHub as a service, not the web-based UI and repository aspect. This is where *Gitea* comes in. It's a FOSS self-hosted GitHub alternative that from what I've seen seems a bit more lightweight than GitLab. However, even once Gitea is all set up and all my repositories are migrated to it, I unfortunately cannot delete my GitHub repositories right away. Soon, I'll need to make university applications to major in Computer Science, and it's likely that a GitHub account will be expected to act as a portfolio. So, I should instead set up all my GitHub repositories a /mirrors/ of my primary Gitea repos until they are no longer necessary. Speaking of portfolios, this leads to my next point.
|
||||
|
||||
* Remake my website.
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: remake-my-website.
|
||||
:END:
|
||||
Yes, again, I know. My current website setup is a simple landing page at [[https://elnu.com/][elnu.com]] with links to [[https://tegakituesday.com/][Tegaki Tuesday]], etc. and some recently watched anime using [[https://anilist.gitbook.io/anilist-apiv2-docs/][AniList's API]], along with [[https://blog.elnu.com/][blog.elnu.com]] for miscellaneous content (been a while since I posted there). What I need to do is once I have all my public repositories on GitHub cleaned up and migrated to Gitea, I should make a portfolio list of all the various projects I've worked on of note, since I don't really have something like that at the moment.
|
||||
|
||||
* Set up an email server.
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: set-up-an-email-server.
|
||||
:END:
|
||||
(TODO: look into [[https://www.youtube.com/watch?v=9zP7qooM4pY][Luke Smith's video on the matter]]) Currently, I'm using ProtonMail. It's good and all, but unfortunately I have to pay in order to be able to use my own [cite/t:@elnu.com] address, and my parents need vanity URLs for another domain name, so it'd be rather annoying to have to pay for other accounts since my plan is only limited to one domain. In the past we've tried doing email through DreamHost, but it seemed like Gmail would shadow block all of those emails. I've heard that self-hosting email can be a huge pain and Gmail (and potentially even setting up Cloudflare reverse DDNS) may be an issue, but it's worth giving a go.
|
||||
|
||||
* Look into NixOS's viability as an Arch alternative, both for desktop and server use.
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: look-into-nixoss-viability-as-an-arch-alternative-both-for-desktop-and-server-use.
|
||||
:END:
|
||||
My one point of frustration with Arch Linux is a "do it yourself distro" is the lack of a built-in way to keep track of configs. In your configuration process, you have to install various packages, run commands, and edit config files across your system. If you didn't keep a meticulous record of all of your steps, then you might have trouble settings everything up again on a second system. Even if you did, then you still have to make record of those tweaks that are made past the initial setup. The fact of the matter is, I'm lazy. I haven't bothered to create an install script for my current Arch configuration, or even a dotfiles repository.
|
||||
|
||||
Now, I've just gotten a new laptop, a Lenovo ThinkPad X220, and I'd like to have a painless method of keeping my system configurations synced between my desktop and laptop.
|
||||
|
||||
This is where NixOS comes in: a Linux distribution based on the Nix package manager that provides reproducible, declarative, and reliable system configuration. Instead of installing packages in the command line (though you can still do that if necessary for packages you only need temporarily), you declare them in the system configuration file =/etc/nixos/configuration.nix=. In this file, you can configure anything you can imagine: users and their permissions, what display manager and window manager/desktop environment you want (if any), how GRUB should be configured, etc.
|
||||
|
||||
In addition, NixOS provides Home Manager (which I haven't much looked into yet), a similar system, except for particular users' configuration. It gives much more fine-grain control, and lets you manage individual programs' configurations, and even have programs installed for one particular user.
|
||||
|
||||
The magic of this is you can set up a Git repository for all of your Nix (=.nix=) configuration files and keep it synced between multiple devices, meaning you can maintain a consistent user experience across devices. Just partition your drive, copy over your configuration, build, and you're ready to go. And I haven't even mentioned NixOS's rollback system!
|
||||
|
||||
Currently, I messing with NixOS in a virtual machine following [[https://www.youtube.com/watch?v=AGVXJ-TIv3Y][this]] (lengthy) tutorial by Matthias Benaets. Hopefully, once I get a configuration set up in there more or less matching my current desktop setup, I can get it transferred to & and set up on the ThinkPad! We'll see; I'm still not completely certain whether NixOS is practical as a desktop distribution.
|
@ -0,0 +1,87 @@
|
||||
#+TITLE: The Joy of Data Processing
|
||||
#+DATE: 2022-07-27
|
||||
#+FILETAGS: programming japanese
|
||||
|
||||
For a project I'm working on (I'll make a post about it once it's done), I needed a large list of Japanese words, with the requirements being that the words be short and without kanji (only in hiragana). In addition, ideally they should be simple words that the average Japanese learner would know, and must be in a machine-readable format that I can use in JavaScript.
|
||||
|
||||
Luckily, I found something that matches these criteria! A [[https://www.jlptmatome.com/jlpt-n5-vocabulary-list/][JLPT N5 vocabulary list from JLPT Matome]] that covers 549 words, which should hopefully be more than enough for my needs.
|
||||
|
||||
However, the list is in the form of HTML tables, and so it's going to needs some work. In addition, the pronunciation is provided only in romaji, so we'll need to convert it to hiragana.
|
||||
|
||||
This is where the joy of data processing and web scraping comes in: refining a data source step-by-step until it's something you can work with. This post is going to be less of a tutorial and more of a walk-through of a process that I found fun.
|
||||
|
||||
* Extracting the raw data
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: extracting-the-raw-data
|
||||
:END:
|
||||
The first step is to convert the tables to a [[https://en.wikipedia.org/wiki/Comma-separated_values][CSV file]]. If you're unfamiliar with CSV, it's basically the most basic way of storing spreadsheet data in a file. On each line, there's a comma-separated list of values (hence it's name, a /Comma-Separated Values/ file), one for each column, and each line corresponds with a row.
|
||||
|
||||
There are various command-line applications and browser extensions that convert HTML to CSV, but what I ended up using is [[https://www.convertcsv.com/html-table-to-csv.htm][this website]]. Using a website for conversions is cringe, I know, but when you're doing a one-off thing and don't need to write any scripts to do the job, utility websites are often the easiest way to get the job done.
|
||||
|
||||
It's pretty handy, and it does the URL fetching for you. The only annoyance was that the source was paginated onto 11 pages, so I had to do each page separately then put them together, but after that was all done I had a nice CSV file:
|
||||
|
||||
#+begin_example
|
||||
1,あげる,ageru,to give
|
||||
2,朝,asa,morning
|
||||
3,封筒,fuutou,envelope
|
||||
4,冬,fuyu,winter
|
||||
5,五,go,five
|
||||
...
|
||||
#+end_example
|
||||
|
||||
Now, this is more data than we need. I only need the third column with the pronunciations (we'll convert these into hiragana later), so one can remove the first, second, and fourth column in some spreadsheet software like LibreOffice and then reexport a single-column CSV file with just the romaji. Once that's done, we have a text file that is just a list of pronunciations:
|
||||
|
||||
#+begin_example
|
||||
ageru
|
||||
asa
|
||||
fuutou
|
||||
fuyu
|
||||
go
|
||||
...
|
||||
#+end_example
|
||||
|
||||
* Converting to hiragana
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: converting-to-hiragana
|
||||
:END:
|
||||
After a bit of research, I found [[https://github.com/koozaki/romaji-conv][koozaki/romaji-conv]], an [[https://www.npmjs.com/package/@koozaki/romaji-conv][npm package]] that does exactly what I need. There's a [[https://romaji-conv.koozaki.com/][web-based demo]] that you can use if you want to quickly try it out or do a quick conversion, but it also has a CLI (command-line interface), which is what we'll use.
|
||||
|
||||
Assuming you already have Node.js and npm installed, you can globally install it with the following command (=i= is a an alias for =install=, =-g= installs the package globally to your system instead of to a particular project's =node_modules=):
|
||||
|
||||
#+begin_src sh
|
||||
npm i -g @koozaki/romaji-conv
|
||||
#+end_src
|
||||
|
||||
We can use the following command to run romaji-conv on each line of our romaji file, =romaji.csv=, and push the resulting hiragana version to a new file, =hiragana.csv=. Previously, I wasn't familiar with =xargs= but I found this solution thanks to [[https://stackoverflow.com/a/29836986][this Stack Overflow answer]].
|
||||
|
||||
#+begin_src sh
|
||||
cat romaji.csv | xargs -L1 romaji-conv > hiragana.csv
|
||||
#+end_src
|
||||
|
||||
* Converting into a JSON list
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: converting-into-a-json-list
|
||||
:END:
|
||||
The final step is to convert this simple text file list into a JSON list/array that we can put directly into our JavaScript for use, in a format such as the following:
|
||||
|
||||
#+begin_example
|
||||
["あげる", "あさ", "ふうとう", "ふゆ", "ご", ...]
|
||||
#+end_example
|
||||
|
||||
To do this, all we need to do is a simple find-and-replace in any text editor, first replacing each new line =\n= with =", "=, and then finally adding the starting =["= and closing ="]=.
|
||||
|
||||
We're done!
|
||||
|
||||
* Closing thoughts
|
||||
:PROPERTIES:
|
||||
:CUSTOM_ID: closing-thoughts
|
||||
:END:
|
||||
The things I've done here by themselves are not in any way groundbreaking. I could have done some things more elegantly, and honestly none of this is anything to call home about. However, I wrote this post anyway because I wanted to show the power of step-by-step data processing and manipulation, and how one with a bit of time one can mutate data sources into whatever form is needed.
|
||||
|
||||
If you see data in tables or some other form online, don't give up! Getting it into the form you need isn't going to be as hard or time-consuming as you think.
|
||||
|
||||
If you're interested in Japanese text manipulation, please do check out Kojiro Ozaki's romaji-conv on GitHub and give it a star! :star: It's super handy and easy to use, and is painfully underrated at only 8 stars (including mine) at the time of writing.
|
||||
|
||||
See you in the next post!
|
||||
|
||||
じゃーね!
|
@ -0,0 +1,12 @@
|
||||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
with pkgs;
|
||||
|
||||
mkShell {
|
||||
buildInputs = [
|
||||
emacs-nox
|
||||
hugo
|
||||
stork
|
||||
html-tidy
|
||||
];
|
||||
}
|
@ -1,34 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
main() {
|
||||
declare hugo_publish_dir=public
|
||||
|
||||
declare stork_config_file=stork.config.json # generated by hugo
|
||||
declare stork_index_file=home.st # generated by stork (json suffix triggers gzip/br compression)
|
||||
|
||||
declare stork_arch=stork-ubuntu-20-04
|
||||
declare stork_releases=https://files.stork-search.net/releases
|
||||
declare stork_version=1.5.0
|
||||
|
||||
declare stork_exec=${stork_arch}-${stork_version}
|
||||
declare stork_url=${stork_releases}/v${stork_version}/${stork_arch}
|
||||
|
||||
# Install Stork if it's not already installed.
|
||||
if [[ ! -f "${stork_exec}" ]]; then
|
||||
echo -e "\nInstalling Stork...\n"
|
||||
wget --no-verbose "${stork_url}" ||
|
||||
{ echo "Error: unable to wget ${stork_url}"; exit 1; }
|
||||
mv "${stork_arch}" "${stork_exec}" ||
|
||||
{ echo "Error: unable to mv ${stork_arch} ${stork_exec}"; exit 1; }
|
||||
chmod +x "${stork_exec}" ||
|
||||
{ echo "Error: unable to chmod ${stork_exec}"; exit 1; }
|
||||
fi
|
||||
|
||||
# Build the Stork index.
|
||||
echo -e "\nBuilding Stork index...\n"
|
||||
./${stork_exec} build --input "${hugo_publish_dir}/${stork_config_file}" --output "${hugo_publish_dir}/${stork_index_file}" ||
|
||||
{ echo "Error: unable to run stork"; exit 1; }
|
||||
}
|
||||
|
||||
set -euo pipefail
|
||||
main "$@"
|
Loading…
Reference in new issue