mirror of
https://github.com/jbranchaud/til
synced 2026-01-18 06:28:02 +00:00
Compare commits
12 Commits
cb4d240c73
...
fa55891c05
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fa55891c05 | ||
|
|
eb3369d296 | ||
|
|
6f47e2f057 | ||
|
|
409201611f | ||
|
|
77cc07a908 | ||
|
|
633c1fa0a5 | ||
|
|
96c394c198 | ||
|
|
0251157dc4 | ||
|
|
97c8701a5a | ||
|
|
1fd64e478a | ||
|
|
8ea123369b | ||
|
|
bc767a0ad3 |
11
README.md
11
README.md
@@ -10,7 +10,7 @@ pairing with smart people at Hashrocket.
|
||||
|
||||
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
|
||||
|
||||
_1580 TILs and counting..._
|
||||
_1589 TILs and counting..._
|
||||
|
||||
See some of the other learning resources I work on:
|
||||
- [Ruby Operator Lookup](https://www.visualmode.dev/ruby-operators)
|
||||
@@ -356,6 +356,7 @@ See some of the other learning resources I work on:
|
||||
- [Quicker Commit Fixes With The Fixup Flag](git/quicker-commit-fixes-with-the-fixup-flag.md)
|
||||
- [Rebase Commits With An Arbitrary Command](git/rebase-commits-with-an-arbitrary-command.md)
|
||||
- [Reference A Commit Via Commit Message Pattern Matching](git/reference-a-commit-via-commit-message-pattern-matching.md)
|
||||
- [Reference Commits Earlier Than Reflog Remembers](git/reference-commits-earlier-than-reflog-remembers.md)
|
||||
- [Remove Untracked Files From A Directory](git/remove-untracked-files-from-a-directory.md)
|
||||
- [Rename A Remote](git/rename-a-remote.md)
|
||||
- [Renaming A Branch](git/renaming-a-branch.md)
|
||||
@@ -407,6 +408,7 @@ See some of the other learning resources I work on:
|
||||
- [Disable A Workflow With The gh CLI](github-actions/disable-a-workflow-with-the-gh-cli.md)
|
||||
- [Reference An Encrypted Secret In An Action](github-actions/reference-an-encrypted-secret-in-an-action.md)
|
||||
- [Trigger A Workflow Via An API Call](github-actions/trigger-a-workflow-via-an-api-call.md)
|
||||
- [Use Labels To Block PR Merge](github-actions/use-labels-to-block-pr-merge.md)
|
||||
|
||||
### Go
|
||||
|
||||
@@ -669,6 +671,7 @@ See some of the other learning resources I work on:
|
||||
- [Set A Window To Its Default Zoom Level](mac/set-a-window-to-its-default-zoom-level.md)
|
||||
- [Specify App When Opening From Command Line](mac/specify-app-when-opening-from-command-line.md)
|
||||
- [Start Amphetamine Session With AppleScript](mac/start-amphetamine-session-with-applescript.md)
|
||||
- [Uninstall LogiTech G Hub From Mac](mac/uninstall-logitech-g-hub-from-mac.md)
|
||||
- [Use A Different Font With iTerm2](mac/use-a-different-font-with-iterm2.md)
|
||||
- [Use Default Screenshot Shortcuts With CleanShot X](mac/use-default-screenshot-shortcuts-with-cleanshot-x.md)
|
||||
- [View All Windows Of The Current App](mac/view-all-windows-of-the-current-app.md)
|
||||
@@ -899,6 +902,7 @@ See some of the other learning resources I work on:
|
||||
- [Timestamp Functions](postgres/timestamp-functions.md)
|
||||
- [Toggling The Pager In PSQL](postgres/toggling-the-pager-in-psql.md)
|
||||
- [Track psql History Separately Per Database](postgres/track-psql-history-separately-per-database.md)
|
||||
- [Trim Leading And Trailing Space From String](postgres/trim-leading-and-trailing-space-from-string.md)
|
||||
- [Truncate All Rows](postgres/truncate-all-rows.md)
|
||||
- [Truncate Tables With Dependents](postgres/truncate-tables-with-dependents.md)
|
||||
- [Turning Timing On](postgres/turn-timing-on.md)
|
||||
@@ -991,6 +995,7 @@ See some of the other learning resources I work on:
|
||||
- [Define The Root Path For The App](rails/define-the-root-path-for-the-app.md)
|
||||
- [Delete Paranoid Records](rails/delete-paranoid-records.md)
|
||||
- [Demodulize A Class Name](rails/demodulize-a-class-name.md)
|
||||
- [Determine The Configured Primary Key Type](rails/determine-the-configured-primary-key-type.md)
|
||||
- [Different Ways To Add A Foreign Key Reference](rails/different-ways-to-add-a-foreign-key-reference.md)
|
||||
- [Disambiguate Where In A Joined Relation](rails/disambiguate-where-in-a-joined-relation.md)
|
||||
- [Empty find_by Returns First Record](rails/empty-find-by-returns-first-record.md)
|
||||
@@ -1276,6 +1281,7 @@ See some of the other learning resources I work on:
|
||||
- [Exit A Process With An Error Message](ruby/exit-a-process-with-an-error-message.md)
|
||||
- [Expect A Method To Be Called And Actually Call It](ruby/expect-a-method-to-be-called-and-actually-call-it.md)
|
||||
- [Extract A Column Of Data From A CSV File](ruby/extract-a-column-of-data-from-a-csv-file.md)
|
||||
- [Extract Capture Group Matches With String Slices](ruby/extract-capture-group-matches-with-string-slices.md)
|
||||
- [FactoryGirl Sequences](ruby/factory-girl-sequences.md)
|
||||
- [Fail](ruby/fail.md)
|
||||
- [Fetch Warns About Superseding Block Argument](ruby/fetch-warns-about-superseding-block-argument.md)
|
||||
@@ -1494,6 +1500,7 @@ See some of the other learning resources I work on:
|
||||
- [Count The Lines In A CSV Where A Column Is Empty](unix/count-the-lines-in-a-csv-where-a-column-is-empty.md)
|
||||
- [Count The Number Of Matches In A Grep](unix/count-the-number-of-matches-in-a-grep.md)
|
||||
- [Count The Number Of ripgrep Pattern Matches](unix/count-the-number-of-ripgrep-pattern-matches.md)
|
||||
- [Count The Number Of Words On A Webpage](unix/count-the-number-of-words-on-a-webpage.md)
|
||||
- [Create A File Descriptor with Process Substitution](unix/create-a-file-descriptor-with-process-substitution.md)
|
||||
- [Create A Sequence Of Values With A Step](unix/create-a-sequence-of-values-with-a-step.md)
|
||||
- [Curl With Cookies](unix/curl-with-cookies.md)
|
||||
@@ -1530,6 +1537,7 @@ See some of the other learning resources I work on:
|
||||
- [Get Matching Filenames As Output From Grep](unix/get-matching-filenames-as-output-from-grep.md)
|
||||
- [Get The SHA256 Hash For A File](unix/get-the-sha256-hash-for-a-file.md)
|
||||
- [Get The Unix Timestamp](unix/get-the-unix-timestamp.md)
|
||||
- [Get Word Count For All Files In Git Repo](unix/get-word-count-for-all-files-in-git-repo.md)
|
||||
- [Global Substitution On The Previous Command](unix/global-substitution-on-the-previous-command.md)
|
||||
- [Globbing For All Directories In Zsh](unix/globbing-for-all-directories-in-zsh.md)
|
||||
- [Globbing For Filenames In Zsh](unix/globbing-for-filenames-in-zsh.md)
|
||||
@@ -1548,6 +1556,7 @@ See some of the other learning resources I work on:
|
||||
- [Killing A Frozen SSH Session](unix/killing-a-frozen-ssh-session.md)
|
||||
- [Last Argument Of The Last Command](unix/last-argument-of-the-last-command.md)
|
||||
- [Less With Style](unix/less-with-style.md)
|
||||
- [Limit Protocols Used In A cURL Command](unix/limit-protocols-used-in-a-curl-command.md)
|
||||
- [List All Fonts On Your Machine](unix/list-all-fonts-on-your-machine.md)
|
||||
- [List All The Enabled ZSH Options](unix/list-all-the-enabled-zsh-options.md)
|
||||
- [List All Users](unix/list-all-users.md)
|
||||
|
||||
34
git/reference-commits-earlier-than-reflog-remembers.md
Normal file
34
git/reference-commits-earlier-than-reflog-remembers.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Reference Commits Earlier Than Reflog Remembers
|
||||
|
||||
While preparing some stats for a recent blog post on [A Decade of
|
||||
TILs](https://www.visualmode.dev/a-decade-of-tils), I ran into an issue
|
||||
referencing chuncks of time further back than 2020.
|
||||
|
||||
```bash
|
||||
❯ git diff --diff-filter=A --name-only HEAD@{2016-02-06}..HEAD@{2017-02-06} -- "*.md"
|
||||
warning: log for 'HEAD' only goes back to Sun, 20 Dec 2020 00:26:27 -0600
|
||||
warning: log for 'HEAD' only goes back to Sun, 20 Dec 2020 00:26:27 -0600
|
||||
```
|
||||
|
||||
This is because `HEAD@...` is a reference to the `reflog`. The `reflog` is a
|
||||
local-only log of objects and activity in the repository. That date looks
|
||||
suspiciously like the time that I got this specific machine and cloned the
|
||||
repo.
|
||||
|
||||
In order to access this information, I need a different approach of finding
|
||||
references that bound these points in time.
|
||||
|
||||
How about asking `rev-list` for the first commit it can find before the given
|
||||
dates in 2017 and 2016 and then using those.
|
||||
|
||||
```bash
|
||||
❯ git rev-list -1 --before="2017-02-07 00:00" HEAD
|
||||
17db6bc4468616786a8f597a10d252c24183d82e
|
||||
|
||||
❯ git rev-list -1 --before="2016-02-07 00:00" HEAD
|
||||
f1d3d1f796007662ff448d6ba0e3bbf38a2b858d
|
||||
|
||||
❯ git diff --diff-filter=A --name-only f1d3d1f796007662ff448d6ba0e3bbf38a2b858d..17db6bc4468616786a8f597a10d252c24183d82e -- "*.md"
|
||||
|
||||
# git outputs a bunch of files ...
|
||||
```
|
||||
41
github-actions/use-labels-to-block-pr-merge.md
Normal file
41
github-actions/use-labels-to-block-pr-merge.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Use Labels To Block PR Merge
|
||||
|
||||
Let's say our GitHub project has custom tags for both `no merge` and `wip`
|
||||
(_work in progress_). Whenever either of those labels has been applied to a PR,
|
||||
we want there to be a failed check so as to block the merge. This is useful to
|
||||
ensure automated tools (as well as someone not looking closely enough) don't
|
||||
merge a PR that isn't _ready to go_.
|
||||
|
||||
This can be achieved with a basic GitHub Actions workflow that requires no
|
||||
3rd-party actions. We can add the following as
|
||||
`.github/workflows/block-labeled-prs.yml` in our project.
|
||||
|
||||
```yaml
|
||||
name: Block Labeled PR Merges
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled, opened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
prevent-merge:
|
||||
if: ${{ contains(github.event.*.labels.*.name, 'no merge') || contains(github.event.*.labels.*.name, 'wip') }}
|
||||
name: Prevent Merging
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for label
|
||||
run: |
|
||||
echo "Pull request label prevents merging."
|
||||
echo "Labels: ${{ join(github.event.*.labels.*.name, ', ') }}"
|
||||
echo "Remove the blocking label(s) to skip this check."
|
||||
exit 1
|
||||
```
|
||||
|
||||
This workflow is run when a pull request is opened, when it is edited or
|
||||
synchronized, and when a label change is made. The job `prevent-merge` sees if
|
||||
any of the label names match `no merge` or `wip`. If so, we echo out some
|
||||
details in the ubuntu container and then `exit 1` to fail the check.
|
||||
|
||||
Shoutout to [Jesse Squire's
|
||||
implementation](https://www.jessesquires.com/blog/2021/08/24/useful-label-based-github-actions-workflows/#updated-21-march-2022)
|
||||
which I've heavily borrowed from here.
|
||||
17
mac/uninstall-logitech-g-hub-from-mac.md
Normal file
17
mac/uninstall-logitech-g-hub-from-mac.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Uninstall LogiTech G Hub From Mac
|
||||
|
||||
I rarely uninstall software from my Mac. And unless the software is nice enough
|
||||
to provide a clear 'Uninstall' flow, it is not straightforward how to do it. In
|
||||
fact, it probably varies quite a bit from app to app.
|
||||
|
||||
In the case of LogiTech's G Hub, I was able to find the following instructions
|
||||
for uninstalling it. The thing of note is that the updater app can take an
|
||||
`--uninstall` flag.
|
||||
|
||||
```bash
|
||||
sudo /Applications/lghub.app/Contents/MacOS/lghub_updater.app/Contents/MacOS/lghub_updater --uninstall
|
||||
```
|
||||
|
||||
I still had to remove the app launcher from my `Applications` directory.
|
||||
|
||||
[source](https://www.reddit.com/r/LogitechG/comments/bluth5/comment/lbhctx1/)
|
||||
58
postgres/trim-leading-and-trailing-space-from-string.md
Normal file
58
postgres/trim-leading-and-trailing-space-from-string.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Trim Leading And Trailing Space From String
|
||||
|
||||
PostgreSQL has a bunch of [string
|
||||
functions](https://www.postgresql.org/docs/current/functions-string.html),
|
||||
including several for doing various string trimming.
|
||||
|
||||
We can use the simplest form of `trim` to remove leading and trailing space
|
||||
characters from a string.
|
||||
|
||||
```sql
|
||||
> select trim(' Taco Cat ');
|
||||
+----------+
|
||||
| btrim |
|
||||
|----------|
|
||||
| Taco Cat |
|
||||
+----------+
|
||||
```
|
||||
|
||||
The syntax for calling `trim` is a bit odd relative to other PostgreSQL
|
||||
functions and functions in other languages. Here is the "grammar" as described
|
||||
in the docs:
|
||||
|
||||
```
|
||||
trim ( [ LEADING | TRAILING | BOTH ] [ characters text ] FROM string text ) → text
|
||||
```
|
||||
|
||||
We pick `leading`, `trailing`, or `both`, with `both` being the default. Then
|
||||
we specify the character(s) we want to remove. This is also optional, the
|
||||
default being the space character. Then we say `from` what string we want to
|
||||
trim those characters.
|
||||
|
||||
Here we remove all sequential spaces from `both` ends of the given string:
|
||||
|
||||
```sql
|
||||
> select trim(both from ' Taco Cat ');
|
||||
+----------+
|
||||
| btrim |
|
||||
|----------|
|
||||
| Taco Cat |
|
||||
+----------+
|
||||
```
|
||||
|
||||
To further demonstrate how `trim` works, here we remove all sequences made up
|
||||
of any of spaces, uppercase `T`, and lowercase `t` from `both` ends of the
|
||||
string:
|
||||
|
||||
```sql
|
||||
> select trim(both ' Tt' from ' Taco Cat ');
|
||||
+--------+
|
||||
| btrim |
|
||||
|--------|
|
||||
| aco Ca |
|
||||
+--------+
|
||||
```
|
||||
|
||||
Notice that in all the above examples the column name of the result is `btrim`.
|
||||
That's probably because `btrim` (_trim both ends_) is being called under the
|
||||
hood for the `both` option.
|
||||
35
rails/determine-the-configured-primary-key-type.md
Normal file
35
rails/determine-the-configured-primary-key-type.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Determine The Configured Primary Key Type
|
||||
|
||||
I noticed an interesting helper function in the database migration generated by
|
||||
`bin/rails active_storage:install`.
|
||||
|
||||
```ruby
|
||||
class CreateActiveStorageTables < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
# Use Active Record's configured type for primary and foreign keys
|
||||
primary_key_type, foreign_key_type = primary_and_foreign_key_types
|
||||
|
||||
# ...
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def primary_and_foreign_key_types
|
||||
config = Rails.configuration.generators
|
||||
setting = config.options[config.orm][:primary_key_type]
|
||||
primary_key_type = setting || :primary_key
|
||||
foreign_key_type = setting || :bigint
|
||||
[ primary_key_type, foreign_key_type ]
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
The `primary_and_foreign_key_types` method looks in the generators config for
|
||||
the ORM (`:active_record`) to determine the configured `:primary_key_type`. By
|
||||
default this will return `nil`. This method then uses `:primary_key` as a
|
||||
fallback value which will be `bigint`. That's why the `foreign_key_type` falls
|
||||
back to `:bigint`.
|
||||
|
||||
If desired, this can be manually configured in `config/application.rb` like
|
||||
shown in the [ActiveRecord Migrations
|
||||
docs](https://guides.rubyonrails.org/active_record_migrations.html#enabling-uuids-in-rails).
|
||||
37
ruby/extract-capture-group-matches-with-string-slices.md
Normal file
37
ruby/extract-capture-group-matches-with-string-slices.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Extract Capture Group Matches With String Slices
|
||||
|
||||
Ruby's _string slice_ syntax allows us to use the square brackets to access
|
||||
portions of a string. It's most common to pass positional integer index
|
||||
arguments or a range. However, in true Ruby fashion, another way of thinking
|
||||
about defining the slice of a string is based on a regex match.
|
||||
|
||||
We can pass a regex and an int (specifying which match we want) to extract some
|
||||
portion of a string based on the regex match. That includes capture groups.
|
||||
|
||||
Here are a couple examples of extracting matching capture groups as well as
|
||||
getting the entire regex match:
|
||||
|
||||
```ruby
|
||||
> "me+abc123@email.com"[/.+\+(.+)@(.+)/, 1]
|
||||
=> "abc123"
|
||||
|
||||
> "me+abc123@email.com"[/.+\+(.+)@(.+)/, 2]
|
||||
=> "email.com"
|
||||
|
||||
> "me+abc123@email.com"[/.+\+(.+)@(.+)/, 0]
|
||||
=> "me+abc123@email.com"
|
||||
|
||||
> "me+abc123@email.com"[/.+\+(.+)@(.+)/]
|
||||
=> "me+abc123@email.com"
|
||||
```
|
||||
|
||||
The `0`th match (which is the default) corresponds to the full match. Each
|
||||
integer position after that corresponds to any capture groups. This maps
|
||||
directly to the underlying `MatchData` object:
|
||||
|
||||
```ruby
|
||||
> /.+\+(.+)@(.+)/.match("me+abc123@email.com")
|
||||
=> #<MatchData "me+abc123@email.com" 1:"abc123" 2:"email.com">
|
||||
```
|
||||
|
||||
[source](https://ruby-doc.org/3.3.6/String.html#class-String-label-String+Slices)
|
||||
25
unix/count-the-number-of-words-on-a-webpage.md
Normal file
25
unix/count-the-number-of-words-on-a-webpage.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Count The Number Of Words On A Webpage
|
||||
|
||||
I was reading through a couple sections of the `postfix` documentation and I
|
||||
was astounded at how large the webpage is, and that is just for the `main.cf`
|
||||
file format.
|
||||
|
||||
Curiosity got the best of me and I wanted to get a sense of the magnitude of
|
||||
the page. A word count seemed like a good measure.
|
||||
|
||||
Using `pandoc` and a couple other unix utilities, I was able to quickly get
|
||||
that number.
|
||||
|
||||
```bash
|
||||
curl -s http://www.postfix.org/postconf.5.html\#virtual_mailbox_maps | pandoc -f html -t plain | wc -w
|
||||
88383
|
||||
```
|
||||
|
||||
Generically, that is:
|
||||
|
||||
```bash
|
||||
curl -s url | pandoc -f html -t plain | wc -w
|
||||
```
|
||||
|
||||
Pandoc produces a plain-text version of the HTML page that was pulled in by
|
||||
`curl` and then we use `wc` to get a word (`-w`) count.
|
||||
32
unix/get-word-count-for-all-files-in-git-repo.md
Normal file
32
unix/get-word-count-for-all-files-in-git-repo.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Get Word Count For All Files In Git Repo
|
||||
|
||||
As part of gathering numbers for [A Decade of TILs](), I wanted to get an word
|
||||
count of all the TIL markdown files I've committed to this project over its 10
|
||||
year history. By using `git ls-files` with a pattern, I can get a list of all
|
||||
file names. Then with `xargs` I can pass that entire list to `wc -w` which
|
||||
gives a word count of each. The final line that `wc -w` outputs is a sum total
|
||||
of all the file word counts. Lastly, piping that through `tail -n1` gives me
|
||||
just that last total count line.
|
||||
|
||||
```bash
|
||||
$ git ls-files "*/**.md" | xargs wc -w | tail -n1
|
||||
206816 total
|
||||
```
|
||||
|
||||
Since the `tail -n1` obfuscates what the `wc -w` is doing, here is what that
|
||||
looks like before that final pipe.
|
||||
|
||||
```bash
|
||||
$ git ls-files "*/**.md" | tail -n3 | xargs wc -w
|
||||
115 zsh/add-to-the-path-via-path-array.md
|
||||
190 zsh/link-a-scalar-to-an-array.md
|
||||
214 zsh/use-a-space-to-exclude-command-from-history.md
|
||||
519 total
|
||||
```
|
||||
|
||||
I can even clean up the final output a bit more with `awk`:
|
||||
|
||||
```bash
|
||||
$ git ls-files "*/**.md" | xargs wc -w | tail -n1 | awk '{print $1}'
|
||||
206816
|
||||
```
|
||||
27
unix/limit-protocols-used-in-a-curl-command.md
Normal file
27
unix/limit-protocols-used-in-a-curl-command.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Limit Protocols Used In A cURL Command
|
||||
|
||||
I was about to install [`atuin`](https://github.com/atuinsh/atuin). I went to
|
||||
their _Quick Start_ section to grab whatever command I would need to install
|
||||
it. It was a `curl` statement piped to `sh`. The thing that caught my attention
|
||||
though was I `curl` flag that I didn't recognize — `--proto`.
|
||||
|
||||
> Tells curl to limit what protocols it may use for transfers.
|
||||
|
||||
Using `curl --proto '=https' ...` we can enforce that only an `https` URL can
|
||||
be used in this command.
|
||||
|
||||
Here is what happens if I try to run the `atuin`-provided `curl` command after
|
||||
I have downgraded their URL to be `http`:
|
||||
|
||||
```bash
|
||||
curl --proto '=https' --tlsv1.2 -LsSf http://setup.atuin.sh | sh
|
||||
curl: (1) Protocol "http" not supported or disabled in libcurl
|
||||
```
|
||||
|
||||
It doesn't even attempt the request. The protocol is considered unsupported and
|
||||
the command immediately fails.
|
||||
|
||||
In addition to only installing software we trust, we should make sure we are
|
||||
only doing so over a protocol we trust (namely, `https`).
|
||||
|
||||
See `man curl` for more details, including about the modifiers (`=`, `+`, `-`).
|
||||
@@ -6,7 +6,7 @@ convert it using the `ebook-convert` binary from `Calibre`.
|
||||
First, install `Calibre`:
|
||||
|
||||
```bash
|
||||
$ brew cask install calibre
|
||||
$ brew install --cask calibre
|
||||
```
|
||||
|
||||
Then convert your ePub using `ebook-convert`:
|
||||
|
||||
Reference in New Issue
Block a user