All the browser userscripts that I’ve been using were scattered across a
multitude of pastebin links and git repositories. It became a bit of effort to
track them down to re-install in Greasemonkey (Firefox) or Tampermonkey
(Chrome) with the time expended searching exceeding the time saved using my
extensions. No beuno.
Enter Browser Bits: a curation of userscripts. (See awesome-userscripts to
learn more!) Ultimately, the repository will contain Stylus stylesheet
overrides and related hodgepodge.
My top two userscripts
Have a taste of my simplistic userscript utilization. It keeps me from
breaking the computer, … erm, website.
MediaWiki Sidebar Toggle
I use my mediawiki sidebar toggle userscript almost daily (click to install).
NethackWiki, Wikipedia, and more operate on the mediawiki stack. The classic
Vector theme employs an obstinate sidebar devoid of a toggle button. This
script adds a toggle back in and binds it to Control-'.
Advent of Code Adjustments
adventofcode.com has rather distracting styling (click to install). From text
“glow” in lieu of typographically-sane emboldened text to a font choice which
fails to be legible with a light foreground and dark background, it can use
improvement. This userscript does not specify a replacement font, no. That’s
for the user to decide! Merely specify your default fonts correctly and the
userscript will just work.
You can customize websites too
Install Tampermonkey or Greasemonkey then copy-paste a template into a new
userscript. Give it a shot. If you can pick up a modicum of the browser
Javascript APIs, you can inject stylesheets, remove unwanted elements, and
more.
As a bonus, if you post the script to a public URL ending in .user.js
visitors well be greeted with a prompt asking to install the userscript. This
couldn’t be easier! Or you could be like me, dump the scripts into a Git
repository.
Keep it simple. The web sucks a lot more than it ever did—vis a vis, the web
from the first half of my life—however all hope is not lost. You too can fix
up the web enough to keep your technological sanity in check.
PS 2024 in Review in the works
I’ve been reading through my journals, all 70,000 words from 2024. It’s a
slog. Stay tuned for a 2024 in Review post once my
reading-while-navel-gazing-project arrives at completion.
Every December since 2015, Advent of Code publishes a Christmas Advent calendar
loaded to the brim with challenging programming quibbles and trials. Advent of
code can be solved in any programming environment, from Microsoft Excel to
Rust, if you can write it, you can use it. In fact, my 2020 Advent of Code
“challenge” (or “theme”) was a different language every day. Nope, I didn’t
finish all 25 problems, though it was a heck of a lot of fun to practice Forth,
various assembly languages, code golfing languages like CJam, variousLisps,
Standard ML, even a real weird language called ZKL with a .howza() method
in its base API.
AoC particitation is not limited to the days in December; all challenges are
always available once published. Some people like myself do enjoy solving the
problems as if it were a race, but I assure you, all you need to do is just log
in with GitHub, Google, or another logic method then get coding!
Edit: Learn about the making of Advent of Code
After publishing this post I stumbled across this talk by Eric Wastl, the fellow
who created AoC on the creation of the project.
My 2024 Goals
I don’t expect to complete AoC 2024 before the end of December, though I will
achieve my own practice goals. My goal this year is to practice bog-standard
Python with the allowance for click to simplify things.
Here is the basic outline of my Python solutions:
import click
@click.command()
@click.argument('puzzle-input', type=click.File())
defmain(puzzle_input):
for line in puzzle_input:
...# Read the input into an appropriate data structure. part1 = part2 =0# Initialize the variables to contain the solutions....# Crunch numbers for a solution!# Print the two solutions out with one per line. click.echo(part1)
click.echo(part2)
if __name__ =='__main__':
main()
Then I run it using python3 day01.py input.txt. Using click it’s about a
line of code to add a --debug flag:
@click.command()
@click.argument('puzzle-input', type=click.File())
@click.option('--debug', is_flag=True)
defmain(puzzle_input, debug):
if debug:
# Fire up the debugger. breakpoint()
...
By the way, turns out you don’t need two statements to start the
built-in Python debugger.
import pdb; pdb.set_trace() simplifies to breakpoint(). instead.
A secondary goal for AoC 2024 is to learn more Perl 5.
A tertiary goal is to practice Go and C.
SPOILERS: AoC 20204, day 1 remarks
This discussion is typed up after completion of the first few days. There be
spoilers beyond this sentence, take heed! My goal here is to detail the
solution for day 1 and discuss some of the language features used.
import click
import re
@click.command()
@click.argument('input', type=click.File())
defmain(input):
L1, L2 = [], []
for line in input:
a, b = [int(i) for i in re.split(r'\s+', line) if i]
L1.append(a)
L2.append(b)
print(sum(abs(a - b) for a, b in zip(sorted(L1), sorted(L2))))
print(sum(a * L2.count(a) for a in L1))
if __name__ =='__main__':
main()
First, import click for the command line argument parsing and re for
regular expression searching. Next declare the program entrypoint main().
The @click... lines are decorator calls. These calls “wrap up” main with its
own to change its behavior, in our case, produce a convenient command line
interface without minimal effort.
In the body of the main() function, click has already opened the file and
bound its file-object to input. Using a for loop, we iterate over each
line. Next we construct a list comprehension: [int(i) for i in re.split(r'\s+', line) if i] that (1) splits the line on groups of whitespace,
(2) converts each non-whitespace group of characters into a number or discards
it in the off chance that leading or trailing white space existed.
This line also assigns to variables a and b. This assignment acts like an
assertion because the list comprehension’s result must have two objects in it
or a ValueError is raised. Here’s an example:
L = list(range(3))
a, b, c = L
print(f'a got {a}')
try:
p, q = L # FailsexceptValueErroras e:
print(f'Got an error: {e.__class__.__name__}: {e}')
a got 0
Got an error: ValueError: too many values to unpack (expected 2)
p, q = L throws an exception: ValueError: too many values to unpack (expected 2).
Back to the day 1 Python. In the body, read the input file object line by
line. The first and second number read on the line are assigned to a and
b. L1.append(a) appends a to the first list, and L2.append(b) appends
to the second.
Next let’s break down two concise generator comprehensions (notice no square or
curly brackets):
print(sum(abs(a - b) for a, b in zip(sorted(L1), sorted(L2))))
print(sum(a * L2.count(a) for a in L1))
Both of the next lines contain the form print(sum(...)). That is, summates
(and prints the total of) all the elements in the iterable sequence (a
generator expression in both casees. Here’s a simpler example that sums all
the square of each natural number less than 10:
print(sum(i**2for i in range(10)))
285
This example also contains a generator expression. Notice no square brackets
and a for keyword in the middle of a line. Think list comprehension, but
only runs if the code actually iterates through the generator. The iterator
pattern can be thought of a simulated form of lazy evaluation, a coding
paradigm popular in functional programming where code is only ran if the result
is used in some way e.g. saved to disk or displayed to the user.
Now that we understand generator expressions and print and sum let’s talk about
each generator expression within print(sum(...)). The first
print(sum(...)) contains following generator expression: abs(a - b) for a, b in zip(sorted(L1), sorted(L2)). The sorted(...) function iterates over the
iterable (in this case each sorted() iterates over one of L1 or L2) and
builds a new iterable of the lists’s sorted contents. Next zip() is used to
combine the two sorted lists into pairs of numbers, one from each list, thereby
lining up the pair of inputs according to part1. The least number in L1 is
now paired with least in L2, and the the most with the most in the other).
For each of these zip()‘ed pairs, the code sums up the difference between a
and b then takes its absolute value (no negatives!). This sum is the answer
for part 1.
And within the second print(sum(...)) is a * L2.count(a) for a in L1). For
every a in L1, count how many times a occurs in L2 then multiple it
against a itself. Sum all these values up for the answer of part 2.
Whew, I think that’s everything.
Other language learnings
I detailed the Python solution in this post, but also have take-aways for some
of the other languages that I solved this day in.
C’s qsort() is for sorting arrays
I was not to keen on writing a sorting algorithm to use in C. After a couple
Googles, I stumbled across qsort().
In my C solution, I chose to use arrays of unsigned int for storage. Here is
how I used qsort() in my solution:
Memory start address (a1 and a2 in the above example).
The number of elements (lines in the above example).
The size of each element (sizeof(unsigned int) in the above example).
A function pointer that compare two elements and returns a number with a
different sign based on its sorted order. In this case I wrote cmpuintp
that subtracts the two unsigned integers and returns the difference.
Perl’s grep and list vs scalar context
Perl’s grep returns the matches in a list value of matching elements but
since it returns a list, it can be used in a scalar context to gets its
length. Compare the two outputs:
$p2 contains the answer for part 2 after this code runs. The return value of
grep is automatically converted into a scalar context because the *
operator works on scalars only.
Will you play AoC?
Take up whatever language you’re learning or practicing. Give it a go. It’s
fun and easy to integrate into coding practice. Code well.
Sidenote: Is this website broken?
Yep, woops! I broke my theme used on winny.tech and blog.winny.tech! I’m
working on it.
Previously I introduced the reader to ShellCheck. In this post I detail
how I use Flycheck in Emacs and offer an Emacs function to automatically
suppress Shellcheck errors at the current line.
I’m an avid Emacs user and it follows that I’ve set up editor customization to
exude the most from ShellCheck. If you, dear reader, are not an Emacs user, I
cannot help you! Please, for the love of shell scripts, ensure ShellCheck
works within your preferred text editor, lest you wish to ship edgecased buggy
scripts!
$ shellcheck --version || echo 'No shellcheck found :('
ShellCheck - shell script analysis tool
version: 0.10.0
license: GNU General Public License, version 3
website: https://www.shellcheck.net
Next — this is the only mandatory Emacs step — install flycheck. The
simplest way is to Type M-x package-install RET flycheck RET followed by M-x global-flycheck-mode RET, though I personally employ use-package to declare
my package usage up front. Here is what I have checked into my Emacs
configuration git repository:
(use-package flycheck
:ensure t :init
(global-flycheck-mode 1))
After global-flycheck-mode is enabled, you’ll see errors underlined in
prog-mode derived buffers. Try C-c ! l to list all errors in another
buffer. Or C-c ! n or C-c ! p to go to next or previous error. See M-x describe-command RET flycheck-mode RET and its complimentary Flycheck
documentation website for further learnings.
If you want to learn more about other keys in the C-c ! prefix from within
Emacs, check out which-key for Emacs or read the flycheck.el sources (or
try M-x describe-variable RET flycheck-mode-map RET from within Emacs).
But, ShellCheck likes to complain
Like all powerful tools, ShellCheck has its tradeoffs. It can save loads of
time avoiding bugs in scripts later when already placed in production. On the
other hand, ShellCheck can slow down development because one has to fix every
single trifle to quell ShellCheck. A savvy shell scripter will find themself
encountering Shellcheck errors that are intentional. Consider this Bash
function that decrypts a pass managed login credential (source):
decrypt(){ local file
file="${PASSWORD_STORE_DIR}/${1}.gpg"if[[ ! -f $file ]]; then echo "No such entry \"${1}\"" >&2return1fi# shellcheck disable=SC2086 gpg $PASSWORD_STORE_GPG_OPTS --quiet --decrypt "$file"}
Without the # shellcheck disable=...ignore directive, which itself is a
comment (a line that begins with #[POSIX]), ShellCheck unyieldingly
complains about the unquoted use of $PASSWORD_STORE_GPG_OPTS.
Time and time again, I’ve forgotten the exact syntax of this ShellCheck
directive. Moreover, resultant of my forgetfulness, I’ve devised a solution to
automatically generate these comment lines in Emacs! Simply move the point to
the line with a ShellCheck error (try C-c ! n or C-c ! p to find the next
or previous error) then type C-c ! k to generate the necessary comment to
shut up ShellCheck.
I’ve embedded the Elisp here for easy copy-paste and to discuss the anatomy of
the code:
(require 'cl) ; for cl-loop(require 'sh-script) ; For sh-mode-map(defun winny--extract-shellcheck-error (err)
(and-let* (((eq (flycheck-error-checker err) 'sh-shellcheck)))
(flycheck-error-id err)))
(defun winny/shellcheck-disable-at-line ()
"Insert \"# shellcheck disable=SC...\" line to silence shellcheck errors." (interactive)
(save-match-data
(save-excursion
(and-let* ((errs
(cl-loop for err in (flycheck-overlay-errors-in (pos-bol) (pos-eol))
if (winny--extract-shellcheck-error err)
collect (winny--extract-shellcheck-error err))))
(beginning-of-line)
(insert (format"# shellcheck disable=%s" (mapconcat'identity errs ",")))
(indent-according-to-mode)
(newline-and-indent)))))
(add-hook 'sh-mode-hook (defun winny--bind-shellcheck-disable ()
(define-key sh-mode-map (kbd "C-c ! k") 'winny/shellcheck-disable-at-line)))
As a sort of preamble, this code ensures cl-loop and sh-mode-map variables
are in scope using the two require forms.
Then, this code defines a helper function winny--extract-shellcheck-error to
determine if a flycheck-error object is a shellcheck error and return the
SC prefixed identifier string. Next comes the interactive commandwinny/shellcheck-disable-at-line which searches for unaddressed ShellCheck
errors on the current line then inserts a comment directive above the current
line in order to silence those ShellCheck errors.
Finally, the add-hook function call adds a function named
winny--bind-shellcheck-disable to execute whenever sh-mode is used. It has
one job: bind winny/shellcheck-disable-at-line to a key.
Notice the usage of a defun instead of a lambda for add-hook’s second
argument. I believe it prudent to name your anonymous functions such that the
M-x describe-variable (C-h v) output is less busy and the code itself is
accessible by name for further monkeying about with M-x eval-expression
(M-:) andfriends.
Conclusion
Equipped with this post, I hope the reader has ShellCheck set up in Emacs. If
Emacs isn’t your prefererred editor, worry not, there is surely a blogpost or
official documentation describing how to set up ShellCheck for your favorite
editor!
Stay tuned for a post on how to set up ShellCheck with pre-commit to ensure
code quality with team-based projects.