1
0
mirror of https://github.com/jbranchaud/til synced 2026-01-06 00:28:01 +00:00

Compare commits

...

115 Commits

Author SHA1 Message Date
copilot-swe-agent[bot]
c58bfbb37f Reorder commands: commit before pull --rebase
Co-authored-by: jbranchaud <694063+jbranchaud@users.noreply.github.com>
2026-01-01 00:18:25 +00:00
copilot-swe-agent[bot]
9b5af6a535 Simplify pull command to use configured upstream
Co-authored-by: jbranchaud <694063+jbranchaud@users.noreply.github.com>
2026-01-01 00:17:23 +00:00
copilot-swe-agent[bot]
62d194f492 Add git pull --rebase to notes:push task
Co-authored-by: jbranchaud <694063+jbranchaud@users.noreply.github.com>
2026-01-01 00:16:23 +00:00
copilot-swe-agent[bot]
7a7a0faf94 Initial plan 2026-01-01 00:14:07 +00:00
jbranchaud
d980514bff Add Create Interactive Picker For Set Of Subtasks as a Taskfile TIL 2025-12-31 12:33:50 -07:00
jbranchaud
db26fc97c6 Add Set Up Forwarding Prefix For Nested Session as a tmux TIL 2025-12-31 12:10:52 -07:00
jbranchaud
8094448877 Add Join URI Path Parts as a Ruby TIL 2025-12-30 10:39:03 -07:00
jbranchaud
883b3e6ee6 Add Run Dev Processes With Overmind Instead Of Foreman as a Rails TIL 2025-12-28 10:21:35 -06:00
jbranchaud
57c4954d6f Add Regenerate Lock File With Newer Bundler as a Ruby TIL 2025-12-26 11:33:07 -06:00
jbranchaud
86a7815a9f Add Specify Default Team And App For Project as a Heroku TIL 2025-12-22 20:46:45 -06:00
jbranchaud
676038e992 Add Create A Module Of Utility Functions as a Ruby TIL 2025-12-17 16:54:43 -06:00
jbranchaud
01fd503a92 Add Run Rails Console With Remote Dokku App as a Rails TIL 2025-12-15 22:27:47 -06:00
jbranchaud
8b718aee4f Add Describe Current Changes And Create New Change as a jj TIL 2025-12-10 11:34:13 -06:00
jbranchaud
88f49de7f3 Add Reference The Full Match In The Replacement as a Sed TIL 2025-12-01 14:07:25 -06:00
jbranchaud
9f9fce7835 Add Make Structs Easier To Use With Keyword Initialization as a Ruby TIL 2025-12-01 06:35:43 -06:00
jbranchaud
65a4d0ef3d Add Rename A Bunch Of Files By Constructing mv Commands as a Unix TIL 2025-11-30 20:10:59 -06:00
jbranchaud
6c8a5eb36d Add new newsletter URL 2025-11-29 15:10:43 -06:00
jbranchaud
fed722d7fe Add Access Your GitHub Profile Photo as a GitHub TIL 2025-11-29 14:40:34 -06:00
jbranchaud
fbebc3e5ee Add Install And Require Gems Inline Without Gemfile as a Ruby TIL 2025-11-28 23:24:00 -06:00
jbranchaud
83d55c420e Add Use .ruby Extension For Template File as a Rails TIL 2025-11-27 10:29:56 -06:00
jbranchaud
8dbbfe0eda Add Create Mock Class That Can Be Overridden as a Ruby TIL 2025-11-25 22:18:00 -06:00
jbranchaud
c38d9f090e Add Monitor Usage Limits From CLI as a Claude Code TIL 2025-11-22 08:43:26 -06:00
jbranchaud
bae3527baf Add Determine ipv4 And ipv6 Public IP Addresses as a Unix TIL 2025-11-21 16:43:08 -06:00
jbranchaud
53a0b88eff Add Get Idea Of What Is In A JSON Column as a MySQL TIL 2025-11-20 09:06:08 -06:00
jbranchaud
665c8f994f Add List All Git Aliases From gitconfig as a Git TIL 2025-11-19 14:39:41 -06:00
jbranchaud
5f11b1665b Remove dprint config to never text wrap
This was causing multi-line sequences of text in TIL markdown files to be
unwrapped into one long line.
2025-11-18 13:20:58 -06:00
jbranchaud
ce5ff038c0 Add Generate A Sequence Of Numbered Items as a Unix TIL 2025-11-18 13:20:49 -06:00
jbranchaud
486a6ef5a9 Add Open Current Prompt In Default Editor as a Claude Code TIL 2025-11-18 12:54:50 -06:00
jbranchaud
c1ce559452 Add Add A Bunch Of CLI Utilities With corutils as a Mac TIL 2025-11-16 22:21:45 -06:00
jbranchaud
07c4aa86b7 Add Squash Changes Into Parent Commit Interactively as a jj TIL 2025-11-16 17:31:42 -06:00
jbranchaud
a0c2a29a96 Add See What Databases You Have Access To as a Planetscale TIL 2025-11-16 10:34:44 -06:00
jbranchaud
45b269abf1 Add Launch Some Confetti as a Mac TIL 2025-11-14 18:52:45 -06:00
jbranchaud
44dc6f2b1f Add Install Go Packages In Brewfile as a Brew TIL 2025-11-14 18:19:43 -06:00
jbranchaud
821a7e5c67 Add Do Project Time Tracking From The CLI as a Workflow TIL 2025-11-14 00:32:13 -06:00
jbranchaud
c0f20267bb Add Shorten SSH Commands With Aliases as a Unix TIL 2025-11-12 15:00:19 -06:00
jbranchaud
50deb6175f Add Detect How Long A User Has Been Idle as a Mac TIL 2025-11-11 17:13:36 -06:00
jbranchaud
d1f41884ce Add Set Default Tasks For Rake To Run as a Ruby TIL 2025-11-10 18:06:12 -06:00
jbranchaud
91149fe7cc Add Show Summary Stats For Current Branch as a Git TIL 2025-11-10 17:25:24 -06:00
jbranchaud
e2eb31a4a9 Add Reference Hash Key With Safe Navigation as a Ruby TIL 2025-11-09 17:12:09 -05:00
jbranchaud
113b7b2dfe Add Prevent Sleep With The Caffeinate Command as a Mac TIL 2025-11-09 13:50:03 -05:00
jbranchaud
16074c021f Add Convert JSON Field To Hash With Indifferent Access as a Rails TIL 2025-11-08 08:55:41 -05:00
jbranchaud
9e76753540 Add Open File On Remote Like GitHub as a VSCode TIL 2025-11-03 14:19:14 -05:00
jbranchaud
eb4dea611e Add Inspect Assertions Preventing Sleep as a Mac TIL 2025-11-02 15:21:31 -06:00
jbranchaud
6ef998b024 Add Check If A File Is Under Version Control as a Git TIL 2025-11-02 11:01:30 -06:00
jbranchaud
6e066ec72a Add Target Another Repo When Creating A PR as a GitHub TIL 2025-11-02 08:18:41 -06:00
jbranchaud
060ce8262d Add Set Up A Project-Local Cluster With Postgres.app as a Postgres TIL 2025-11-01 08:41:56 -05:00
jbranchaud
96fd138837 Add Exclude A Specific File From fd Results as a Unix TIL 2025-10-31 16:09:44 -05:00
jbranchaud
a51d716e45 Add Reword A Commit Message With Fugitive as a Vim TIL 2025-10-29 22:26:37 -05:00
jbranchaud
59de2fef0d Add Add Bindings To Split Panes To Current Directory as a tmux TIL 2025-10-29 21:38:06 -05:00
jbranchaud
fdd2461b75 Add Check If A File Has Changed In A Script as a Git TIL 2025-10-28 12:25:43 -05:00
jbranchaud
e8c2e01d6f Add better status check for notes:push 2025-10-28 08:29:15 -05:00
jbranchaud
ed9cedc870 Add Run A Task If It Meets Criteria as a Taskfile TIL 2025-10-28 08:26:39 -05:00
jbranchaud
da585ec5a4 Add Allow Cursor To Be Launched From CLI as a Cursor TIL 2025-10-27 21:37:59 -05:00
jbranchaud
35d1a81ea7 Move recent TIL to new GitHub category 2025-10-27 17:08:55 -05:00
jbranchaud
d69fefe9f0 Use status instead of precondition to avoid error 1 2025-10-27 17:07:21 -05:00
jbranchaud
1cc612294e Run notes:push steps only if there are changes 2025-10-27 16:45:03 -05:00
jbranchaud
d79264395b Add Open A PR To An Unforked Repo as a GitHub TIL 2025-10-27 15:14:00 -05:00
jbranchaud
2d5abd9cbf Revert to excluding entire README formatting
The `unorderedListMarker` setting was hallucinated.
2025-10-26 18:20:47 -05:00
jbranchaud
2efaf27066 Add Tell gh What The Default Repo Is as a Git TIL 2025-10-26 17:58:42 -05:00
jbranchaud
6b4b2c588c Rework notes task to make edit the primary one
What was 'edit' has been renamed to 'open'. And 'edit' is now what was
not so clearly named 'save'.
2025-10-26 17:14:51 -05:00
jbranchaud
e473fa781d Move notes higher up, it is most common command 2025-10-26 17:09:51 -05:00
jbranchaud
5ce5eccb0a Add Jump Between Changes In Current File as a Neovim TIL 2025-10-26 16:52:24 -05:00
jbranchaud
db4961a8eb Don't error if you escape from fzf 2025-10-25 13:40:17 -05:00
jbranchaud
ff227a39ed Turn of only dprint's unordered list formatting 2025-10-25 12:51:25 -05:00
jbranchaud
0d3975eb9c Disable dprint for README for bullet formatting 2025-10-25 12:49:33 -05:00
jbranchaud
d171c3784b Get rid of the help task, not needed now 2025-10-25 11:51:27 -05:00
jbranchaud
e6d00a94f3 Add an interactive picker for notes tasks 2025-10-25 11:50:22 -05:00
jbranchaud
0e934d8dd3 Add a default task to list all tasks 2025-10-25 10:05:38 -05:00
jbranchaud
c30b17dd68 Add a taskfile for managing notes submodule 2025-10-25 09:59:34 -05:00
jbranchaud
757e163c2e Ignore all changes to the notes submodule 2025-10-25 00:57:38 -05:00
jbranchaud
cf037f13f7 Tell the submodule to use the main branch 2025-10-25 00:50:16 -05:00
jbranchaud
08fb235e81 Add submodule for including private notes file 2025-10-25 00:37:04 -05:00
jbranchaud
95dc00d748 Add Get The Names Of The Month as a Ruby TIL 2025-10-24 15:18:40 -05:00
jbranchaud
ece12aac76 Add Clean Up Your Brew Installations as a Brew TIL 2025-10-23 14:23:11 -05:00
jbranchaud
9a6a40bdd6 Add Capture Screenshot To Clipboard From CLI as a Mac TIL 2025-10-22 15:40:50 -05:00
Jake Worth
4b4bd2350f Fix typo 2025-10-19 22:44:16 -05:00
jbranchaud
5924edf4c0 Add Set Up GPG Signing Key as a Git commit 2025-10-19 15:06:09 -05:00
jbranchaud
5eb21b3aa2 Add Transform Text To Lowercase as a Unix TIL 2025-10-17 21:49:38 -05:00
jbranchaud
5b3f1536fd Add Format Specific html.erb Template Files as a Rails TIL 2025-10-16 08:55:51 -05:00
jbranchaud
ec0e84664f Add Prevent Mailer Previews From Cluttering Database as a Rails TIL 2025-10-06 08:52:25 -05:00
jbranchaud
3912276599 Add Allow Number Input To Accept Decimal Values as an HTML TIL 2025-10-04 10:24:36 -05:00
jbranchaud
d166ffac0b Add Scope Records To A Lower Or Upper Bound as a Rails TIL 2025-10-03 19:59:00 -05:00
jbranchaud
e8b953ba6d Add Disable And Enable A Button as a Tailwind TIL 2025-09-29 18:54:05 -05:00
jbranchaud
8613c21f41 Add Avoid Double Negation With Minitest Refute as a Ruby TIL 2025-09-16 09:33:48 -05:00
jbranchaud
2b5df03981 Fix ordering of two TILs in README 2025-09-16 09:32:46 -05:00
jbranchaud
aef15d53b0 Add Parameterize A String With Underscores as a Rails TIL 2025-09-12 12:05:26 -05:00
jbranchaud
0c31fb6363 Add Clear Entries From Git Stash as a Git TIL 2025-08-05 09:10:20 -05:00
jbranchaud
cb94142042 Add Decompose Unicode Character With Diacritic Mark as a Ruby TIL 2025-07-21 17:38:41 -05:00
jbranchaud
ae2974e3b8 Add Authorize A cURL Request as a Unix TIL 2025-07-02 12:25:32 -05:00
jbranchaud
0ed4d84bc6 Add Get Specific Values From Hashes And Arrays as a Ruby TIL 2025-07-02 10:09:48 -05:00
jbranchaud
3b7e3258fe Add View A Nicely-Formatted CSV In Terminal as a Workflow TIL 2025-06-27 16:13:28 -05:00
jbranchaud
d00796b054 Add Manage Timestamps With Upsert as a Rails TIL 2025-06-26 09:21:17 -05:00
jbranchaud
8fecb0e863 Add References Target Primary Key By Default as a Postgres TIL 2025-06-19 09:59:12 -05:00
jbranchaud
14942c20d7 Add Provide Fake Form Helper To Controllers as a Rails TIL 2025-06-16 10:28:18 -05:00
jbranchaud
e901ae3b77 Add Restore File From One Branch To The Current as a Git TIL 2025-06-11 09:08:59 -05:00
jbranchaud
a4fee08596 Add a couple links to the About section of README 2025-06-09 09:44:40 -05:00
jbranchaud
6e518763c7 Add Check The Size Of Databases In A Cluster as a Postgres TIL 2025-05-06 21:21:57 -05:00
jbranchaud
bb40353512 Add Customize Template For New Schema Migration as a Rails TIL 2025-05-03 21:15:07 -05:00
jbranchaud
917f9e516e Add Cherry Pick Multiple Commits At Once as a git TIL 2025-05-02 23:14:16 -05:00
jbranchaud
d8dfcce0fc Add Format DateTime With Builtin Formats as a Rails TIL 2025-04-30 23:18:32 -05:00
jbranchaud
0d173ccaaf Add Interpret Cron Schedule From The CLI as a Unix TIL 2025-04-24 15:46:19 -05:00
jbranchaud
8dd9f86b80 Add Highlight Small Change On Single Line as a Git TIL 2025-04-17 18:19:12 -05:00
jbranchaud
2bb8af2880 Add Vimium course under other learning resources 2025-04-17 17:22:17 -05:00
jbranchaud
e16c2525be Add Run nvim With Factory Defaults as a Neovim TIL 2025-04-12 16:32:22 -05:00
jbranchaud
a55fff68e1 Add Exclude A Directory During A Command as a Git TIL 2025-04-12 16:03:06 -05:00
jbranchaud
162a7ceea3 Add Format A List Of Items By Locale as a JavaScript TIL 2025-04-11 17:47:33 -05:00
jbranchaud
f578727349 Add Make Neovim The Default Way To View Man Pages as a Unix TIL 2025-04-09 21:25:17 -05:00
jbranchaud
4ba53dca7d Add Create And Execute SQL Statements With \gexec as a Postgres TIL 2025-04-07 17:52:13 -05:00
jbranchaud
571f465fe6 Fix some typos in an old git TIL 2025-04-07 17:18:05 -05:00
jbranchaud
a547b9cee2 Add Create A Filename With The Current Date as a Unix TIL 2025-04-02 09:26:38 -05:00
jbranchaud
99ce5aee7b Add Bypass On-Save Tooling When Writing File as a Vim TIL 2025-04-01 10:56:25 -05:00
jbranchaud
60b6aa40ad Add Open Current Tab In New Window With Vimium as a Chrome TIL 2025-03-31 10:18:13 -05:00
jbranchaud
f97634a61e Add Have Script ShellCheck Itself When Executing as a Unix TIL 2025-03-29 09:20:39 -05:00
jbranchaud
34ba60d313 Add List RDS Snapshots With Matching Identifier Prefix as an AWS TIL 2025-03-28 11:25:31 -05:00
jbranchaud
3a178e901e Add Filter ActiveModel Validation Errors as a Rails TIL 2025-03-27 10:41:35 -05:00
94 changed files with 3437 additions and 7 deletions

5
.gitmodules vendored Normal file
View File

@@ -0,0 +1,5 @@
[submodule "notes"]
path = notes
url = git@github.com:jbranchaud/til-notes-private.git
branch = main
ignore = all

114
README.md
View File

@@ -8,11 +8,13 @@ warrant a full blog post. These are things I've picked up by [Learning In
Public™](https://dev.to/jbranchaud/how-i-built-a-learning-machine-45k9) and
pairing with smart people at Hashrocket.
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
_1628 TILs and counting..._
_1715 TILs and counting..._
See some of the other learning resources I work on:
- [Get Started with Vimium](https://egghead.io/courses/get-started-with-vimium~3t5f7)
- [Ruby Operator Lookup](https://www.visualmode.dev/ruby-operators)
- [Vim Un-Alphabet](https://www.youtube.com/playlist?list=PL46-cKSxMYYCMpzXo6p0Cof8hJInYgohU)
@@ -29,6 +31,7 @@ If you've learned something here, support my efforts writing daily TILs by
* [AWS](#aws)
* [Brew](#brew)
* [Chrome](#chrome)
* [Claude Code](#claude-code)
* [Clojure](#clojure)
* [CSS](#css)
* [Deno](#deno)
@@ -38,6 +41,7 @@ If you've learned something here, support my efforts writing daily TILs by
* [Elixir](#elixir)
* [Gatsby](#gatsby)
* [Git](#git)
* [GitHub](#github)
* [GitHub Actions](#github-actions)
* [Go](#go)
* [GROQ](#groq)
@@ -80,6 +84,7 @@ If you've learned something here, support my efforts writing daily TILs by
* [SQLite](#sqlite)
* [Streaming](#streaming)
* [Tailwind CSS](#tailwind-css)
* [Taskfile](#taskfile)
* [tmux](#tmux)
* [TypeScript](#typescript)
* [Unix](#unix)
@@ -114,6 +119,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [AWS CLI Requires Groff Executable](aws/aws-cli-requires-groff-executable.md)
- [Find And Follow Server Logs](aws/find-and-follow-server-logs.md)
- [List RDS Snapshots With Matching Identifier Prefix](aws/list-rds-snapshots-with-matching-identifier-prefix.md)
- [Output CLI Results In Different Formats](aws/output-cli-results-in-different-formats.md)
- [Sign Up User With Email And Password](aws/sign-up-user-with-email-and-password.md)
- [SSH Into An ECS Container](aws/ssh-into-an-ecs-container.md)
@@ -122,8 +128,10 @@ If you've learned something here, support my efforts writing daily TILs by
### Brew
- [Clean Up Your Brew Installations](brew/clean-up-your-brew-installations.md)
- [Configure Brew Environment Variables](brew/configure-brew-environment-variables.md)
- [Export List Of Everything Installed By Brew](brew/export-list-of-everything-installed-by-brew.md)
- [Install Go Packages In Brewfile](brew/install-go-packages-in-brewfile.md)
- [List All Services Managed By Brew](brew/list-all-services-managed-by-brew.md)
### Chrome
@@ -134,6 +142,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Duplicate The Current Tab](chrome/duplicate-the-current-tab.md)
- [Easier Access To Network Throttling Controls](chrome/easier-access-to-network-throttling-controls.md)
- [Keybinding To Focus The Address Bar](chrome/keybinding-to-focus-the-address-bar.md)
- [Open Current Tab In New Window With Vimium](chrome/open-current-tab-in-new-window-with-vimium.md)
- [Pause JavaScript From The Source DevTools Panel](chrome/pause-javascript-from-the-source-devtools-panel.md)
- [Navigate The Browser History With Vimium](chrome/navigate-the-browser-history-with-vimium.md)
- [Pretty Print Tabular Data](chrome/pretty-print-tabular-data.md)
@@ -146,6 +155,11 @@ If you've learned something here, support my efforts writing daily TILs by
- [Trigger Commands From The Devtools Command Palette](chrome/trigger-commands-from-the-devtools-command-palette.md)
- [View Network Traffic For New Tabs](chrome/view-network-traffic-for-new-tabs.md)
### Claude Code
- [Monitor Usage Limits From CLI](claude-code/monitor-usage-limits-from-cli.md)
- [Open Current Prompt In Default Editor](claude-code/open-current-prompt-in-default-editor.md)
### Clojure
- [Aggregation Using merge-with](clojure/aggregation-using-merge-with.md)
@@ -199,6 +213,10 @@ If you've learned something here, support my efforts writing daily TILs by
- [Style A Background With A Linear Gradient](css/style-a-background-with-a-linear-gradient.md)
- [Using Maps In SCSS](css/using-maps-in-scss.md)
### Cursor
- [Allow Cursor To Be Launched From CLI](cursor/allow-cursor-to-be-launched-from-cli.md)
### Deno
- [Read In The Contents Of A File](deno/read-in-the-contents-of-a-file.md)
@@ -310,13 +328,17 @@ If you've learned something here, support my efforts writing daily TILs by
- [Caching Credentials](git/caching-credentials.md)
- [Change The Start Point Of A Branch](git/change-the-start-point-of-a-branch.md)
- [Check How A File Is Being Ignored](git/check-how-a-file-is-being-ignored.md)
- [Check If A File Has Changed In A Script](git/check-if-a-file-has-changed-in-a-script.md)
- [Check If A File Is Under Version Control](git/check-if-a-file-is-under-version-control.md)
- [Checking Commit Ancestry](git/checking-commit-ancestry.md)
- [Checkout Old Version Of A File](git/checkout-old-version-of-a-file.md)
- [Checkout Previous Branch](git/checkout-previous-branch.md)
- [Cherry Pick A Range Of Commits](git/cherry-pick-a-range-of-commits.md)
- [Cherry Pick Multiple Commits At Once](git/cherry-pick-multiple-commits-at-once.md)
- [Clean Out All Local Branches](git/clean-out-all-local-branches.md)
- [Clean Out Working Copy With Patched Restore](git/clean-out-working-copy-with-patched-restore.md)
- [Clean Up Old Remote Tracking References](git/clean-up-old-remote-tracking-references.md)
- [Clear Entries From Git Stash](git/clear-entries-from-git-stash.md)
- [Clone A Repo Just For The Files, Without History](git/clone-a-repo-just-for-the-files-without-history.md)
- [Clone A Repo Locally From .git](git/clone-a-repo-locally-from-git.md)
- [Configure Global gitignore File](git/configure-global-gitignore-file.md)
@@ -331,6 +353,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Dropping Commits With Git Rebase](git/dropping-commits-with-git-rebase.md)
- [Dry Runs in Git](git/dry-runs-in-git.md)
- [Exclude A File From A Diff Output](git/exclude-a-file-from-a-diff-output.md)
- [Exclude A Directory During A Command](git/exclude-a-directory-during-a-command.md)
- [Excluding Files Locally](git/excluding-files-locally.md)
- [Extend Git With Custom Commands](git/extend-git-with-custom-commands.md)
- [Files With Local Changes Cannot Be Removed](git/files-with-local-changes-cannot-be-removed.md)
@@ -345,6 +368,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Grep For A Pattern On Another Branch](git/grep-for-a-pattern-on-another-branch.md)
- [Grep Over Commit Messages](git/grep-over-commit-messages.md)
- [Highlight Extra Whitespace In Diff Output](git/highlight-extra-whitespace-in-diff-output.md)
- [Highlight Small Change On Single Line](git/highlight-small-change-on-single-line.md)
- [Ignore Changes To A Tracked File](git/ignore-changes-to-a-tracked-file.md)
- [Ignore Files Specific To Your Workflow](git/ignore-files-specific-to-your-workflow.md)
- [Include A Message With Your Stashed Changes](git/include-a-message-with-your-stashed-changes.md)
@@ -357,6 +381,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Last Commit A File Appeared In](git/last-commit-a-file-appeared-in.md)
- [List All Files Added During Span Of Time](git/list-all-files-added-during-span-of-time.md)
- [List All Files Changed Between Two Branches](git/list-all-files-changed-between-two-branches.md)
- [List All Git Aliases From gitconfig](git/list-all-git-aliases-from-gitconfig.md)
- [List Branches That Contain A Commit](git/list-branches-that-contain-a-commit.md)
- [List Commits On A Branch](git/list-commits-on-a-branch.md)
- [List Different Commits Between Two Branches](git/list-different-commits-between-two-branches.md)
@@ -379,10 +404,12 @@ If you've learned something here, support my efforts writing daily TILs by
- [Renaming A Branch](git/renaming-a-branch.md)
- [Resetting A Reset](git/resetting-a-reset.md)
- [Resolve A Merge Conflict From Stash Pop](git/resolve-a-merge-conflict-from-stash-pop.md)
- [Restore File From One Branch To The Current](git/restore-file-from-one-branch-to-the-current.md)
- [Review Commits From Before A Certain Date](git/review-commits-from-before-a-certain-date.md)
- [Run A Git Command From Outside The Repo](git/run-a-git-command-from-outside-the-repo.md)
- [Set A Custom Pager For A Specific Command](git/set-a-custom-pager-for-a-specific-command.md)
- [Set Default Branch Name For New Repos](git/set-default-branch-name-for-new-repos.md)
- [Set Up GPG Signing Key](git/set-up-gpg-signing-key.md)
- [Shorthand To Force Push A Branch](git/shorthand-to-force-push-a-branch.md)
- [Show All Commits For A File Beyond Renaming](git/show-all-commits-for-a-file-beyond-renaming.md)
- [Show Changes For Files That Match A Pattern](git/show-changes-for-files-that-match-a-pattern.md)
@@ -390,6 +417,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Show File Diffs When Viewing Git Log](git/show-file-diffs-when-viewing-git-log.md)
- [Show List Of Most Recently Committed Branches](git/show-list-of-most-recently-committed-branches.md)
- [Show Only Commits That Touch Specific Lines](git/show-only-commits-that-touch-specific-lines.md)
- [Show Summary Stats For Current Branch](git/show-summary-stats-for-current-branch.md)
- [Show The diffstat Summary Of A Commit](git/show-the-diffstat-summary-of-a-commit.md)
- [Show The Good And The Bad With Git Bisect](git/show-the-good-and-the-bad-with-git-bisect.md)
- [Show What Is In A Stash](git/show-what-is-in-a-stash.md)
@@ -418,6 +446,13 @@ If you've learned something here, support my efforts writing daily TILs by
- [What Is The Current Branch?](git/what-is-the-current-branch.md)
- [Whitespace Warnings](git/whitespace-warnings.md)
### GitHub
- [Access Your GitHub Profile Photo](github/access-your-github-profile-photo.md)
- [Open A PR To An Unforked Repo](github/open-a-pr-to-an-unforked-repo.md)
- [Target Another Repo When Creating A PR](github/target-another-repo-when-creating-a-pr.md)
- [Tell gh What The Default Repo Is](github/tell-gh-what-the-default-repo-is.md)
### GitHub Actions
- [Cache Playwright Dependencies Across Workflows](github-actions/cache-playwright-dependencies-across-workflows.md)
@@ -471,11 +506,13 @@ If you've learned something here, support my efforts writing daily TILs by
- [Open Dashboard For Specific Add-On](heroku/open-dashboard-for-specific-add-on.md)
- [Run SQL Against Remote Postgres Database](heroku/run-sql-against-remote-postgres-database.md)
- [Set And Show Heroku Env Variables](heroku/set-and-show-heroku-env-variables.md)
- [Specify Default Team And App For Project](heroku/specify-default-team-and-app-for-project.md)
- [SSH Into Heroku Server Hosting App](heroku/ssh-into-heroku-server-hosting-app.md)
### HTML
- [Adding Alt Text To An Image](html/adding-alt-text-to-an-image.md)
- [Allow Number Input To Accept Decimal Values](html/allow-number-input-to-accept-decimal-values.md)
- [Determine Which Button Submitted The Form](html/determine-which-button-submitted-the-form.md)
- [Disable Auto-Completion For A Form Input](html/disable-auto-completion-for-a-form-input.md)
- [Disclose Additional Details](html/disclose-additional-details.md)
@@ -554,6 +591,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Find Where Yarn Is Installing Binaries](javascript/find-where-yarn-is-installing-binaries.md)
- [for...in Iterates Over Object Properties](javascript/for-in-iterates-over-object-properties.md)
- [Format A Decimal To A Fixed Number Of Digits](javascript/format-a-decimal-to-a-fixed-number-of-digits.md)
- [Format A List Of Items By Locale](javascript/format-a-list-of-items-by-locale.md)
- [Format Time Zone Identifier](javascript/format-time-zone-identifier.md)
- [Formatting Values With Units For Display](javascript/formatting-values-with-units-for-display.md)
- [Freeze An Object, Sorta](javascript/freeze-an-object-sorta.md)
@@ -627,7 +665,9 @@ If you've learned something here, support my efforts writing daily TILs by
### jj
- [Colocate jj And git Directories For Project](jj/colocate-jj-and-git-directories-for-project.md)
- [Describe Current Changes And Create New Change](jj/describe-current-changes-and-create-new-change.md)
- [Find System-wide Config File For User](jj/find-system-wide-config-file-for-user.md)
- [Squash Changes Into Parent Commit Interactively](jj/squash-changes-into-parent-commit-interactively.md)
### jq
@@ -669,18 +709,24 @@ If you've learned something here, support my efforts writing daily TILs by
- [Access All Screen And Video Capture Options](mac/access-all-screen-and-video-capture-options.md)
- [Access System Information On OS X](mac/access-system-information-on-osx.md)
- [Access Unsupported Screen Resolutions With RDM](mac/access-unsupported-screen-resolutions-with-rdm.md)
- [Add A Bunch Of CLI Utilities With coreutils](mac/add-a-bunch-of-cli-utilities-with-coreutils.md)
- [Capture Screenshot To Clipboard From CLI](mac/capture-screenshot-to-clipboard-from-cli.md)
- [Check Network Quality Stats From The Command Line](mac/check-network-quality-stats-from-the-command-line.md)
- [Clean Up Old Homebrew Files](mac/clean-up-old-homebrew-files.md)
- [Convert An HEIC Image File To JPG](mac/convert-an-heic-image-file-to-jpg.md)
- [Default Screenshot Location](mac/default-screenshot-location.md)
- [Detect How Long A User Has Been Idle](mac/detect-how-long-a-user-has-been-idle.md)
- [Disable Swipe Navigation For A Specific App](mac/disable-swipe-navigation-for-a-specific-app.md)
- [Display A Message With Alfred](mac/display-a-message-with-alfred.md)
- [Find The Process Using A Specific Port](mac/find-the-process-using-a-specific-port.md)
- [Gesture For Viewing All Windows Of Current App](mac/gesture-for-viewing-all-windows-of-current-app.md)
- [Insert A Non-Breaking Space Character](mac/insert-a-non-breaking-space-character.md)
- [Inspect Assertions Preventing Sleep](mac/inspect-assertions-preventing-sleep.md)
- [Keyboard Shortcuts For Interesting With Text Areas](mac/keyboard-shortcuts-for-interacting-with-text-areas.md)
- [Launch Some Confetti](mac/launch-some-confetti.md)
- [List All The Say Voices](mac/list-all-the-say-voices.md)
- [Open Finder.app To Specific Directory](mac/open-finder-app-to-specific-directory.md)
- [Prevent Sleep With The Caffeinate Command](mac/prevent-sleep-with-the-caffeinate-command.md)
- [Quickly Type En Dashes And Em Dashes](mac/quickly-type-en-dashes-and-em-dashes.md)
- [Require Additional JS Libraries In Postman](mac/require-additional-js-libraries-in-postman.md)
- [Resize App Windows With AppleScript](mac/resize-app-windows-with-applescript.md)
@@ -721,6 +767,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Doing Date Math](mysql/doing-date-math.md)
- [Dump A Database To A File](mysql/dump-a-database-to-a-file.md)
- [Echo A Message From A SQL File](mysql/echo-a-message-from-a-sql-file.md)
- [Get Idea Of What Is In A JSON Column](mysql/get-idea-of-what-is-in-a-json-column.md)
- [Ignore Duplicates When Inserting Records](mysql/ignore-duplicates-when-inserting-records.md)
- [List Databases And Tables](mysql/list-databases-and-tables.md)
- [Run Statements In A Transaction](mysql/run-statements-in-a-transaction.md)
@@ -734,7 +781,9 @@ If you've learned something here, support my efforts writing daily TILs by
- [Allow Neovim To Copy/Paste With System Clipboard](neovim/allow-neovim-to-copy-paste-with-system-clipboard.md)
- [Create User Command To Open Init Config](neovim/create-user-command-to-open-init-config.md)
- [Jump Between Changes In Current File](neovim/jump-between-changes-in-current-file.md)
- [Run A Lua Statement From The Command Prompt](neovim/run-a-lua-statement-from-the-command-prompt.md)
- [Run nvim With Factory Defaults](neovim/run-nvim-with-factory-defaults.md)
- [Set Up Vim-Plug With Neovim](neovim/set-up-vim-plug-with-neovim.md)
### Netlify
@@ -772,6 +821,7 @@ If you've learned something here, support my efforts writing daily TILs by
### Planetscale
- [See What Databases You Have Access To](planetscale/see-what-databases-you-have-access-to.md)
- [Seed Production Data Into Another Branch](planetscale/seed-production-data-into-another-branch.md)
### pnpm
@@ -798,6 +848,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Check If The Local Server Is Running](postgres/check-if-the-local-server-is-running.md)
- [Check If User Role Exists For Database](postgres/check-if-user-role-exists-for-database.md)
- [Check Table For Any Oprhaned Records](postgres/check-table-for-any-orphaned-records.md)
- [Check The Size Of Databases In A Cluster](postgres/check-the-size-of-databases-in-a-cluster.md)
- [Checking Inequality](postgres/checking-inequality.md)
- [Checking The Type Of A Value](postgres/checking-the-type-of-a-value.md)
- [Clear The Screen In psql](postgres/clear-the-screen-in-psql.md)
@@ -818,6 +869,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Create A Table From The Structure Of Another](postgres/create-a-table-from-the-structure-of-another.md)
- [Create An Index Across Two Columns](postgres/create-an-index-across-two-columns.md)
- [Create An Index Without Locking The Table](postgres/create-an-index-without-locking-the-table.md)
- [Create And Execute SQL Statements With \gexec](postgres/create-and-execute-sql-statements-with-gexec.md)
- [Create Database Uses Template1](postgres/create-database-uses-template1.md)
- [Create hstore From Two Arrays](postgres/create-hstore-from-two-arrays.md)
- [Create Table Adds A Data Type](postgres/create-table-adds-a-data-type.md)
@@ -901,6 +953,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Prevent A Query From Running Too Long](postgres/prevent-a-query-from-running-too-long.md)
- [Print The Query Buffer In psql](postgres/print-the-query-buffer-in-psql.md)
- [Put Unique Constraint On Generated Column](postgres/put-unique-constraint-on-generated-column.md)
- [References Target Primary Key By Default](postgres/references-target-primary-key-by-default.md)
- [Remove Not Null Constraint From A Column](postgres/remove-not-null-constraint-from-a-column.md)
- [Renaming A Sequence](postgres/renaming-a-sequence.md)
- [Renaming A Table](postgres/renaming-a-table.md)
@@ -911,6 +964,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Set Inclusion With hstore](postgres/set-inclusion-with-hstore.md)
- [Set A Seed For The Random Number Generator](postgres/set-a-seed-for-the-random-number-generator.md)
- [Set A Statement Timeout Threshold For A Session](postgres/set-a-statement-timeout-threshold-for-a-session.md)
- [Set Up A Project-Local Cluster With Postgres.app](postgres/set-up-a-project-local-cluster-with-postgres-app.md)
- [Sets With The Values Command](postgres/sets-with-the-values-command.md)
- [Shorthand Absolute Value Operator](postgres/shorthand-absolute-value-operator.md)
- [Show All Versions Of An Operator](postgres/show-all-versions-of-an-operator.md)
@@ -1016,6 +1070,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Comparing DateTimes Down To Second Precision](rails/comparing-datetimes-down-to-second-precision.md)
- [Conditional Class Selectors in Haml](rails/conditional-class-selectors-in-haml.md)
- [Convert A Symbol To A Constant](rails/convert-a-symbol-to-a-constant.md)
- [Convert JSON Field To Hash With Indifferent Access](rails/convert-json-field-to-hash-with-indifferent-access.md)
- [Count The Number Of Records By Attribute](rails/count-the-number-of-records-by-attribute.md)
- [Create A Custom Named References Column](rails/create-a-custom-named-references-column.md)
- [Create A Join Table With The Migration DSL](rails/create-a-join-table-with-the-migration-dsl.md)
@@ -1023,6 +1078,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Creating Records of Has_One Associations](rails/creating-records-of-has-one-associations.md)
- [Custom Validation Message](rails/custom-validation-message.md)
- [Customize Paths And Helpers For Devise Routes](rails/customize-paths-and-helpers-for-devise-routes.md)
- [Customize Template For New Schema Migration](rails/customize-template-for-new-schema-migration.md)
- [Customize The Path Of A Resource Route](rails/customize-the-path-of-a-resource-route.md)
- [Define The Root Path For The App](rails/define-the-root-path-for-the-app.md)
- [Delete Paranoid Records](rails/delete-paranoid-records.md)
@@ -1035,10 +1091,13 @@ If you've learned something here, support my efforts writing daily TILs by
- [Ensure A Rake Task Cannot Write Data](rails/ensure-a-rake-task-cannot-write-data.md)
- [Ensure Migrations Use The Latest Schema](rails/ensure-migrations-use-the-latest-schema.md)
- [Ensure Record Saved With after_commit Callback](rails/ensure-record-saved-with-after-commit-callback.md)
- [Filter ActiveModel Validation Errors](rails/filter-active-model-validation-errors.md)
- [Filter ActiveStorage Blobs To Only Images](rails/filter-active-storage-blobs-to-only-images.md)
- [Find Or Create A Record With FactoryBot](rails/find-or-create-a-record-with-factory-bot.md)
- [Find Records With Multiple Associated Records](rails/find-records-with-multiple-associated-records.md)
- [Force All Users To Sign Out](rails/force-all-users-to-sign-out.md)
- [Format DateTime With Builtin Formats](rails/format-datetime-with-builtin-formats.md)
- [Format Specific html.erb Template Files](rails/format-specific-html-erb-template-files.md)
- [Generate A Model](rails/generate-a-model.md)
- [Generate A Rails App From The Main Branch](rails/generate-a-rails-app-from-the-main-branch.md)
- [Generating And Executing SQL](rails/generating-and-executing-sql.md)
@@ -1068,6 +1127,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Make A String Attribute Easy To Inquire About](rails/make-a-string-attribute-easy-to-inquire-about.md)
- [Make ActionMailer Synchronous In Test](rails/make-action-mailer-synchronous-in-test.md)
- [Make Remove Column Migration Reversible](rails/make-remove-column-migration-reversible.md)
- [Manage Timestamps With Upsert](rails/manage-timestamps-with-upsert.md)
- [Manually Run A Migration From Rails Console](rails/manually-run-a-migration-from-rails-console.md)
- [Mark For Destruction](rails/mark-for-destruction.md)
- [Mask An ActiveRecord Attribute](rails/mask-an-activerecord-attribute.md)
@@ -1076,6 +1136,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Mock Rails Environment With An Inquiry Instance](rails/mock-rails-environment-with-an-inquiry-instance.md)
- [Order Matters For `rescue_from` Blocks](rails/order-matters-for-rescue-from-blocks.md)
- [Override Text Displayed By Form Label](rails/override-text-displayed-by-form-label.md)
- [Parameterize A String With Underscores](rails/parameterize-a-string-with-underscores.md)
- [Params Includes Submission Button Info](rails/params-includes-submission-button-info.md)
- [Params Is A Hash With Indifferent Access](rails/params-is-a-hash-with-indifferent-access.md)
- [Parse Query Params From A URL](rails/parse-query-params-from-a-url.md)
@@ -1084,7 +1145,9 @@ If you've learned something here, support my efforts writing daily TILs by
- [Polymorphic Path Helpers](rails/polymorphic-path-helpers.md)
- [Prefer select_all Over execute For Read Queries](rails/prefer-select-all-over-execute-for-read-queries.md)
- [Pretend Generations](rails/pretend-generations.md)
- [Prevent Mailer Previews From Cluttering Database](rails/prevent-mailer-previews-from-cluttering-database.md)
- [Prevent Writes With A Sandboxed Rails Console](rails/prevent-writes-with-a-sandboxed-rails-console.md)
- [Provide Fake Form Helper To Controllers](rails/provide-fake-form-helper-to-controllers.md)
- [Query A Single Value From The Database](rails/query-a-single-value-from-the-database.md)
- [Read In Environment-Specific Config Values](rails/read-in-environment-specific-config-values.md)
- [Read-Only Models](rails/read-only-models.md)
@@ -1104,9 +1167,12 @@ If you've learned something here, support my efforts writing daily TILs by
- [Rounding Numbers With Precision](rails/rounding-numbers-with-precision.md)
- [Run A Rake Task Programmatically](rails/run-a-rake-task-programmatically.md)
- [Run Commands With Specific Rails Version](rails/run-commands-with-specific-rails-version.md)
- [Run Dev Processes With Overmind Instead Of Foreman](rails/run-dev-processes-with-overmind-instead-of-foreman.md)
- [Run Rails Console With Remote Dokku App](rails/run-rails-console-with-remote-dokku-app.md)
- [Run Some Code Whenever Rails Console Starts](rails/run-some-code-whenever-rails-console-starts.md)
- [Scaffold Auth Functionality With Rails 8 Generator](rails/scaffold-auth-functionality-with-rails-8-generator.md)
- [Schedule Sidekiq Jobs Out Into The Future](rails/schedule-sidekiq-jobs-out-into-the-future.md)
- [Scope Records To A Lower Or Upper Bound](rails/scope-records-to-a-lower-or-upper-bound.md)
- [Secure Passwords With Rails And Bcrypt](rails/secure-passwords-with-rails-and-bcrypt.md)
- [Select A Select By Selector](rails/select-a-select-by-selector.md)
- [Select A Specific Rails Version To Install](rails/select-a-specific-rails-version-to-install.md)
@@ -1135,6 +1201,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Update Column Versus Update Attribute](rails/update-column-versus-update-attribute.md)
- [Upgrading Your Manifest For Sprocket's 4](rails/upgrading-your-manifest-for-sprockets-4.md)
- [Use IRB And Ruby Flags With Rails Console](rails/use-irb-and-ruby-flags-with-rails-console.md)
- [Use .ruby Extension For Template File](rails/use-ruby-extension-for-template-file.md)
- [Useful ActiveSupport Constants For Durations](rails/useful-active-support-constants-for-durations.md)
- [Validate Column Data With Check Constraints](rails/validate-column-data-with-check-constraints.md)
- [Verify And Read A Signed Cookie Value](rails/verify-and-read-a-signed-cookie-value.md)
@@ -1274,8 +1341,9 @@ If you've learned something here, support my efforts writing daily TILs by
- [Add Progress Reporting To Long-Running Script](ruby/add-progress-reporting-to-long-running-script.md)
- [Are They All True?](ruby/are-they-all-true.md)
- [Assert About An Object's Attributes With RSpec](ruby/assert-about-an-objects-attributes-with-rspec.md)
- [Audit Your Ruby Project For Any CVEs](ruby/audit-your-ruby-project-for-any-cves.md)
- [Assoc For Hashes](ruby/assoc-for-hashes.md)
- [Audit Your Ruby Project For Any CVEs](ruby/audit-your-ruby-project-for-any-cves.md)
- [Avoid Double Negation With Minitest Refute](ruby/avoid-double-negation-with-minitest-refute.md)
- [Block Comments](ruby/block-comments.md)
- [Block Syntaxes Have Different Precedence](ruby/block-syntaxes-have-different-precedence.md)
- [Build HTTP And HTTPS URLs](ruby/build-http-and-https-urls.md)
@@ -1295,8 +1363,11 @@ If you've learned something here, support my efforts writing daily TILs by
- [Create a CSV::Table Object](ruby/create-a-csv-table-object.md)
- [Create A Hash From An Array Of Arrays](ruby/create-a-hash-from-an-array-of-arrays.md)
- [Create Listing Of All Middleman Pages](ruby/create-listing-of-all-middleman-pages.md)
- [Create Mock Class That Can Be Overridden](ruby/create-mock-class-that-can-be-overridden.md)
- [Create A Module Of Utility Functions](ruby/create-a-module-of-utility-functions.md)
- [Create Named Structs With Struct.new](ruby/create-named-structs-with-struct-new.md)
- [Create Thumbnail Image For A PDF](ruby/create-thumbnail-image-for-a-pdf.md)
- [Decompose Unicode Character With Diacritic Mark](ruby/decompose-unicode-character-with-diacritic-mark.md)
- [Defaulting To Frozen String Literals](ruby/defaulting-to-frozen-string-literals.md)
- [Define A Custom RSpec Matcher](ruby/define-a-custom-rspec-matcher.md)
- [Define A Method On A Struct](ruby/define-a-method-on-a-struct.md)
@@ -1328,6 +1399,8 @@ If you've learned something here, support my efforts writing daily TILs by
- [Generate A Signed JWT Token](ruby/generate-a-signed-jwt-token.md)
- [Generate Ruby Version And Gemset Files With RVM](ruby/generate-ruby-version-and-gemset-files-with-rvm.md)
- [Get Info About Your RubyGems Environment](ruby/get-info-about-your-ruby-gems-environment.md)
- [Get Specific Values From Arrays And Hashes](ruby/get-specific-values-from-hashes-and-arrays.md)
- [Get The Names Of The Month](ruby/get-the-names-of-the-month.md)
- [Get The Output Of Running A System Program](ruby/get-the-output-of-running-a-system-program.md)
- [Get UTC Offset For Different Time Zones](ruby/get-utc-offset-for-different-time-zones.md)
- [Identify Outdated Gems](ruby/identify-outdated-gems.md)
@@ -1335,15 +1408,18 @@ If you've learned something here, support my efforts writing daily TILs by
- [Iterate With An Offset Index](ruby/iterate-with-an-offset-index.md)
- [Include Extra Context In A Honeybadger Notify](ruby/include-extra-context-in-a-honeybadger-notify.md)
- [Ins And Outs Of Pry](ruby/ins-and-outs-of-pry.md)
- [Install And Require Gems Inline Without Gemfile](ruby/install-and-require-gems-inline-without-gemfile.md)
- [Install Latest Version Of Ruby With asdf](ruby/install-latest-version-of-ruby-with-asdf.md)
- [Invoking Rake Tasks Multiple Times](ruby/invoking-rake-tasks-multiple-times.md)
- [IRB Has Built-In Benchmarking With Ruby 3](ruby/irb-has-built-in-benchmarking-with-ruby-3.md)
- [Join URI Path Parts](ruby/join-uri-path-parts.md)
- [Jump Out Of A Nested Context With Throw/Catch](ruby/jump-out-of-a-nested-context-with-throw-catch.md)
- [Last Raised Exception In The Call Stack](ruby/last-raised-exception-in-the-call-stack.md)
- [Limit Split](ruby/limit-split.md)
- [List The Running Ruby Version](ruby/list-the-running-ruby-version.md)
- [Listing Local Variables](ruby/listing-local-variables.md)
- [Make An Executable Ruby Script](ruby/make-an-executable-ruby-script.md)
- [Make Structs Easier To Use With Keyword Initialization](ruby/make-structs-easier-to-use-with-keyword-initialization.md)
- [Map With Index Over An Array](ruby/map-with-index-over-an-array.md)
- [Mock Method Chain Calls With RSpec](ruby/mock-method-chain-calls-with-rspec.md)
- [Mocking Requests With Partial URIs Using Regex](ruby/mocking-requests-with-partial-uris-using-regex.md)
@@ -1370,6 +1446,8 @@ If you've learned something here, support my efforts writing daily TILs by
- [Rake Only Lists Tasks With Descriptions](ruby/rake-only-lists-tasks-with-descriptions.md)
- [Read The First Line From A File](ruby/read-the-first-line-from-a-file.md)
- [Refer To Implicit Block Argument With It](ruby/refer-to-implicit-block-argument-with-it.md)
- [Reference Hash Key With Safe Navigation](ruby/reference-hash-key-with-safe-navigation.md)
- [Regenerate Lock File With Newer Bundler](ruby/regenerate-lock-file-with-newer-bundler.md)
- [Rendering ERB](ruby/rendering-erb.md)
- [Replace The Current Process With An External Command](ruby/replace-the-current-process-with-an-external-command.md)
- [Require Entire Gemfile In Pry Session](ruby/require-entire-gemfile-in-pry-session.md)
@@ -1384,6 +1462,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Scripting With RVM](ruby/scripting-with-rvm.md)
- [Scroll To Top Of Page With Capybara](ruby/scroll-to-top-of-page-with-capybara.md)
- [Search For Gem Versions Available To Install](ruby/search-for-gem-versions-available-to-install.md)
- [Set Default Tasks For Rake To Run](ruby/set-default-tasks-for-rake-to-run.md)
- [Set RVM Default Ruby](ruby/set-rvm-default-ruby.md)
- [Shift The Month On A Date Object](ruby/shift-the-month-on-a-date-object.md)
- [Show Public Methods With Pry](ruby/show-public-methods-with-pry.md)
@@ -1428,6 +1507,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [OSX sed Does Regex A Bit Different](sed/osx-sed-does-regex-a-bit-different.md)
- [Output Only Lines Involved In A Substitution](sed/output-only-lines-involved-in-a-substitution.md)
- [Reference A Capture In The Regex](sed/reference-a-capture-in-the-regex.md)
- [Reference The Full Match In The Replacement](sed/reference-the-full-match-in-the-replacement.md)
- [Use An Alternative Delimiter In A Substitution](sed/use-an-alternative-delimiter-in-a-substitution.md)
### Shell
@@ -1448,12 +1528,19 @@ If you've learned something here, support my efforts writing daily TILs by
- [Apply Tailwind Classes To Existing CSS Class](tailwind/apply-tailwind-classes-to-existing-css-class.md)
- [Base Styles For Text Link](tailwind/base-styles-for-text-link.md)
- [Disable And Enable A Button](tailwind/disable-and-enable-a-button.md)
- [Specify Paths For Purging Unused CSS](tailwind/specify-paths-for-purging-unused-css.md)
- [Use Tailwind Typography Prose In Dark Mode](tailwind/use-tailwind-typography-prose-in-dark-mode.md)
### Taskfile
- [Create Interactive Picker For Set Of Subtasks](taskfile/create-interactive-picker-for-set-of-subtasks.md)
- [Run A Task If It Meets Criteria](taskfile/run-a-task-if-it-meets-criteria.md)
### tmux
- [Access Past Copy Buffer History](tmux/access-past-copy-buffer-history.md)
- [Add Bindings To Split Panes To Current Directory](tmux/add-bindings-to-split-panes-to-current-directory.md)
- [Adjusting Window Pane Size](tmux/adjusting-window-pane-size.md)
- [Break Current Pane Out To Separate Window](tmux/break-current-pane-out-to-separate-window.md)
- [Change Base Directory Of Existing Session](tmux/change-base-directory-of-existing-session.md)
@@ -1483,6 +1570,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Reset An Option Back To Its Default Value](tmux/reset-an-option-back-to-its-default-value.md)
- [Set Environment Variables When Creating Session](tmux/set-environment-variables-when-creating-session.md)
- [Set Session Specific Environment Variables](tmux/set-session-specific-environment-variables.md)
- [Set Up Forwarding Prefix For Nested Session](tmux/set-up-forwarding-prefix-for-nested-session.md)
- [Show The Current Value For An Option](tmux/show-the-current-value-for-an-option.md)
- [Swap Split Panes](tmux/swap-split-panes.md)
- [Switch To A Specific Session And Window](tmux/switch-to-a-specific-session-and-window.md)
@@ -1516,6 +1604,7 @@ If you've learned something here, support my efforts writing daily TILs by
### Unix
- [All The Environment Variables](unix/all-the-environment-variables.md)
- [Authorize A cURL Request](unix/authorize-a-curl-request.md)
- [Cat A File With Line Numbers](unix/cat-a-file-with-line-numbers.md)
- [Cat Files With Color Using Bat](unix/cat-files-with-color-using-bat.md)
- [Change Default Shell For A User](unix/change-default-shell-for-a-user.md)
@@ -1539,10 +1628,12 @@ If you've learned something here, support my efforts writing daily TILs by
- [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 Filename With The Current Date](unix/create-a-filename-with-the-current-date.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)
- [Curling For Headers](unix/curling-for-headers.md)
- [Curling With Basic Auth Credentials](unix/curling-with-basic-auth-credentials.md)
- [Determine ipv4 And ipv6 Public IP Addresses](unix/determine-ipv4-and-ipv6-public-ip-addresses.md)
- [Different Ways To Generate A v4 UUID](unix/different-ways-to-generate-a-v4-uuid.md)
- [Display All The Terminal Colors](unix/display-all-the-terminal-colors.md)
- [Display Free Disk Space](unix/display-free-disk-space.md)
@@ -1553,6 +1644,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Enable Multi-Select Of Results With fzf](unix/enable-multi-select-of-results-with-fzf.md)
- [Exclude A Command From The ZSH History File](unix/exclude-a-command-from-the-zsh-history-file.md)
- [Exclude A Directory With Find](unix/exclude-a-directory-with-find.md)
- [Exclude A Specific File From fd Results](unix/exclude-a-specific-file-from-fd-results.md)
- [Exclude Certain Files From An rsync Run](unix/exclude-certain-files-from-an-rsync-run.md)
- [Figure Out The Week Of The Year From The Terminal](unix/figure-out-the-week-of-the-year-from-the-terminal.md)
- [File Type Info With File](unix/file-type-info-with-file.md)
@@ -1572,6 +1664,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Fix Unlinked Node Binaries With asdf](unix/fix-unlinked-node-binaries-with-asdf.md)
- [Forward Multiple Ports Over SSH](unix/forward-multiple-ports-over-ssh.md)
- [Generate A SAML Key And Certificate Pair](unix/generate-a-saml-key-and-certificate-pair.md)
- [Generate A Sequence Of Numbered Items](unix/generate-a-sequence-of-numbered-items.md)
- [Generate Base64 Encoding Without Newlines](unix/generate-base64-encoding-without-newlines.md)
- [Generate Random 20-Character Hex String](unix/generate-random-20-character-hex-string.md)
- [Get A List Of Locales On Your System](unix/get-a-list-of-locales-on-your-system.md)
@@ -1586,12 +1679,14 @@ If you've learned something here, support my efforts writing daily TILs by
- [Grep For Files Without A Match](unix/grep-for-files-without-a-match.md)
- [Grep For Files With Multiple Matches](unix/grep-for-files-with-multiple-matches.md)
- [Grep For Multiple Patterns](unix/grep-for-multiple-patterns.md)
- [Have Script ShellCheck Itself When Executing](unix/have-script-shellcheck-itself-when-executing.md)
- [Hexdump A Compiled File](unix/hexdump-a-compiled-file.md)
- [Ignore A Directory During ripgrep Search](unix/ignore-a-directory-during-ripgrep-search.md)
- [Ignore The Alias When Running A Command](unix/ignore-the-alias-when-running-a-command.md)
- [Include Ignore Files In Ripgrep Search](unix/include-ignore-files-in-ripgrep-search.md)
- [Interactively Browse Available Node Versions](unix/interactively-browse-availabile-node-versions.md)
- [Interactively Switch asdf Package Versions](unix/interactively-switch-asdf-package-versions.md)
- [Interpret Cron Schedule From The CLI](unix/interpret-cron-schedule-from-the-cli.md)
- [Jump To The Ends Of Your Shell History](unix/jump-to-the-ends-of-your-shell-history.md)
- [Kill Everything Running On A Certain Port](unix/kill-everything-running-on-a-certain-port.md)
- [Killing A Frozen SSH Session](unix/killing-a-frozen-ssh-session.md)
@@ -1614,6 +1709,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Load Env Vars In Bash Script](unix/load-env-vars-in-bash-script.md)
- [Look Through All Files That Have Been Git Stashed](unix/look-through-all-files-that-have-been-git-stashed.md)
- [Make Direnv Less Noisy](unix/make-direnv-less-noisy.md)
- [Make Neovim The Default Way To View Man Pages](unix/make-neovim-the-default-way-to-view-man-pages.md)
- [Manually Pass Two Git Files To Delta](unix/manually-pass-two-git-files-to-delta.md)
- [Map A Domain To localhost](unix/map-a-domain-to-localhost.md)
- [Negative Look-Ahead Search With ripgrep](unix/negative-look-ahead-search-with-ripgrep.md)
@@ -1631,6 +1727,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Produce A Lowercase V4 UUID](unix/produce-a-lowercase-v4-uuid.md)
- [Provide A Fallback Value For Unset Parameter](unix/provide-a-fallback-value-for-unset-parameter.md)
- [Remove A Directory Called `-p`](unix/remove-a-directory-called-dash-p.md)
- [Rename A Bunch Of Files By Constructing mv Commands](unix/rename-a-bunch-of-files-by-constructing-mv-commands.md)
- [Repeat Yourself](unix/repeat-yourself.md)
- [Replace Pattern Across Many Files In A Project](unix/replace-pattern-across-many-files-in-a-project.md)
- [Run A Command Repeatedly Several Times](unix/run-a-command-repeatedly-several-times.md)
@@ -1644,6 +1741,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Securely Remove Files](unix/securely-remove-files.md)
- [See Where asdf Gets Current Tool Version](unix/see-where-asdf-gets-current-tool-version.md)
- [Set The asdf Package Version For A Single Shell](unix/set-the-asdf-package-version-for-a-single-shell.md)
- [Shorten SSH Commands With Aliases](unix/shorten-ssh-commands-with-aliases.md)
- [Show A File Preview When Searching With FZF](unix/show-a-file-preview-when-searching-with-fzf.md)
- [Show Disk Usage For The Current Directory](unix/show-disk-usage-for-the-current-directory.md)
- [Show The Size Of Everything In A Directory](unix/show-the-size-of-everything-in-a-directory.md)
@@ -1655,6 +1753,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Switch Versions of a Brew Formula](unix/switch-versions-of-a-brew-formula.md)
- [Tell direnv To Load The Env File](unix/tell-direnv-to-load-the-env-file.md)
- [Touch Access And Modify Times Individually](unix/touch-access-and-modify-times-individually.md)
- [Transform Text To Lowercase](unix/transform-text-to-lowercase.md)
- [Type Fewer Paths With Brace Expansion](unix/type-fewer-paths-with-brace-expansion.md)
- [Undo Changes Made To Current Terminal Prompt](unix/undo-changes-made-to-current-terminal-prompt.md)
- [Undo Some Command Line Editing](unix/undo-some-command-line-editing.md)
@@ -1696,6 +1795,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Breaking The Undo Sequence](vim/breaking-the-undo-sequence.md)
- [Buffer Time Travel](vim/buffer-time-travel.md)
- [Build And Install A Go Program](vim/build-and-install-a-go-program.md)
- [Bypass On-Save Tooling When Writing File](vim/bypass-on-save-tooling-when-writing-file.md)
- [Case-Aware Substitution With vim-abolish](vim/case-aware-substitution-with-vim-abolish.md)
- [Case-Insensitive Substitution](vim/case-insensitive-substitution.md)
- [Center The Cursor](vim/center-the-cursor.md)
@@ -1798,6 +1898,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Replace A Character](vim/replace-a-character.md)
- [Reset Target tslime Pane](vim/reset-target-tslime-pane.md)
- [Reverse A Group Of Lines](vim/reverse-a-group-of-lines.md)
- [Reword A Commit Message With Fugitive](vim/reword-a-commit-message-with-fugitive.md)
- [Rotate Everything By 13 Letters](vim/rotate-everything-by-13-letters.md)
- [Rotate The Orientation Of Split Windows](vim/rotate-the-orientation-of-split-windows.md)
- [Running Bundle With vim-bundler](vim/running-bundle-with-vim-bundler.md)
@@ -1846,6 +1947,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Find The Location Of User Settings JSON File](vscode/find-the-location-of-user-settings-json-file.md)
- [Jump To Problems In The Current File](vscode/jump-to-problems-in-the-current-file.md)
- [Open An Integrated Terminal Window](vscode/open-an-integrated-terminal-window.md)
- [Open File On Remote Like GitHub](vscode/open-file-on-remote-like-github.md)
- [Pop Open The Quick Fix Window](vscode/pop-open-the-quick-fix-window.md)
- [Step Through Project-Wide Search Results](vscode/step-through-project-wide-search-results.md)
- [Synchronize Vim Clipboard With System Clipboard](vscode/synchronize-vim-clipboard-with-system-clipboard.md)
@@ -1875,6 +1977,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Create A Local Sanity Dataset Backup](workflow/create-a-local-sanity-dataset-backup.md)
- [Create A Public URL For A Local Server](workflow/create-a-public-url-for-a-local-server.md)
- [Create Todo Items In Logseq](workflow/create-todo-items-in-logseq.md)
- [Do Project Time Tracking From The CLI](workflow/do-project-time-tracking-from-the-cli.md)
- [Enable Dev Tools For Safari](workflow/enable-dev-tools-for-safari.md)
- [Forward Stripe Events To Local Server](workflow/forward-stripe-events-to-local-server.md)
- [Get URL For GitHub User Profile Photo](workflow/get-url-for-github-user-profile-photo.md)
@@ -1893,6 +1996,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Temporarily Hide CleanShot X Capture Previews](workflow/temporarily-hide-cleanshot-x-capture-previews.md)
- [Toggle Between Stories In Storybook](workflow/toggle-between-stories-in-storybook.md)
- [Update asdf Plugins With Latest Package Versions](workflow/update-asdf-plugins-with-latest-package-versions.md)
- [View A Nicely-Formatted CSV In Terminal](workflow/view-a-nicely-formatted-csv-in-terminal.md)
- [View The PR For The Current GitHub Branch](workflow/view-the-pr-for-the-current-github-branch.md)
### XState
@@ -1937,6 +2041,10 @@ current number of TILs and display the result in the command tray.
## About
I've written more about how this repo came to be in [How I Built a Learning
Machine](https://dev.to/jbranchaud/how-i-built-a-learning-machine-45k9) and [A
Decade of TILs](https://www.visualmode.dev/a-decade-of-tils).
I shamelessly stole this idea from
[thoughtbot/til](https://github.com/thoughtbot/til).

79
Taskfile.yml Normal file
View File

@@ -0,0 +1,79 @@
version: '3'
vars:
NOTES_DIR: notes
NOTES_FILE: '{{.NOTES_DIR}}/NOTES.md'
EDITOR: '{{.EDITOR | default "nvim"}}'
tasks:
default:
desc: Show available commands
cmds:
- task --list
notes:
desc: Interactive picker for notes tasks
cmds:
- |
TASK=$(task --list | grep "^\* notes:" | sed 's/^\* notes://' | sed 's/\s\+/ - /' | fzf --prompt="Select notes task: " --height=40% --reverse) || true
if [ -n "$TASK" ]; then
TASK_NAME=$(echo "$TASK" | awk '{print $1}' | sed 's/:$//')
task notes:$TASK_NAME
fi
interactive: true
silent: true
notes:edit:
desc: All-in-one edit, commit, and push notes
cmds:
- task notes:open
- task notes:push
notes:sync:
desc: Sync latest changes from the notes submodule
cmds:
- git submodule update --remote {{.NOTES_DIR}}
- cd {{.NOTES_DIR}} && git checkout main
silent: false
notes:open:
desc: Opens NOTES.md (syncs latest changes first) in default editor
deps: [notes:sync]
cmds:
- $EDITOR {{.NOTES_FILE}}
interactive: true
notes:push:
desc: Commit and push changes to notes submodule
dir: '{{.NOTES_DIR}}'
cmds:
- git add NOTES.md
- git commit -m "Update notes - $(date '+%Y-%m-%d %H:%M')"
- git pull --rebase
- git push
status:
- git diff --exit-code NOTES.md
silent: false
notes:status:
desc: Check status of notes submodule
dir: '{{.NOTES_DIR}}'
cmds:
- git status
notes:pull:
desc: Pull latest changes (alias for sync)
cmds:
- task notes:sync
notes:diff:
desc: Show uncommitted changes in notes
dir: '{{.NOTES_DIR}}'
cmds:
- git diff NOTES.md
notes:log:
desc: Show recent commit history for notes
dir: '{{.NOTES_DIR}}'
cmds:
- git log --oneline -10

View File

@@ -0,0 +1,29 @@
# List RDS Snapshots With Matching Identifier Prefix
I'm working on a script that manually creates a snapshot which it will then
restore to a temporary database that I can scrub and dump. The snapshots that
this script takes are _manual_ and they are named with identifiers that have a
defining prefix (`dev-snapshot-`). Besides the few snapshots created by this
script, there are tons of automated snapshots that RDS creates for
backup/recovery purposes.
I want to list any snapshots that have been created by the script. I can do
this with the `describe-db-snapshots` command and some filters.
```bash
$ aws rds describe-db-snapshots \
--snapshot-type manual \
--query "DBSnapshots[?starts_with(DBSnapshotIdentifier, 'dev-snapshot-')].DBSnapshotIdentifier" \
--no-cli-pager
[
"dev-snapshot-20250327-155355"
]
```
There are two key pieces. The `--snapshot-type manual` filter excludes all
those automated snapshots. The `--query` both filters to any snapshots whose
identifier `?starts_with` the prefix `dev-snapshot-` and then refines the
output to just the `DBSnapshotIdentifier` instead of the entire JSON object.
[source](https://docs.aws.amazon.com/cli/latest/reference/rds/describe-db-snapshots.html)

View File

@@ -0,0 +1,40 @@
# Clean Up Your Brew Installations
Over time as you upgrade brew-installed programs and make changes to your
`Brewfile`, your machine will have artifacts left behind that you no longer
need.
Periodically, it is good to clean things up.
First, you can get a summary of stale and outdated files that brew has
installed. Use the `--dry-run` flag.
```bash
$ brew cleanup --dry-run
```
If you feel good about what you see in the output, then give things a clean.
```bash
$ brew cleanup
```
Second, if you are using a `Brewfile` to manage what `brew` installs, then you
can instruct `brew` to uninstall any dependencies that aren't specified in that
file.
By default it operates as a dry run and the `--force` flag will be needed to
actually do the cleanup. And specify the filename if it doesn't match the
default of `Brewfile`.
```bash
$ brew bundle cleanup --file=Brewfile.personal
```
If the output looks good, then force the cleanup:
```bash
$ brew bundle cleanup --force --file=Brewfile.personal
```
See `brew cleanup --help` and `brew bundle --help` for more details.

View File

@@ -0,0 +1,27 @@
# Install Go Packages In Brewfile
Typically my `Brewfile` is only full of `brew` and `cask` directives. That's
starting to change now that `brew` supports installing Go packages listed in the
`Brewfile`.
Use the `go` directive and the URL to the hosted Go package.
Here is an example of a `Brewfile` that includes a `cask`, `brew`, and `go`
directive.
```
# screen resolution tool
cask "betterdisplay"
# Mac keychain management, gpg key
brew "pinentry-mac"
# Sanitized production Postgres dumps
go "github.com/jackc/pg_partialcopy"
```
I've recently added the exact package from above to my [`dotfiles`
repo](https://github.com/jbranchaud/dotfiles/commit/e83e9d19504f0e2f95eba33123f907f999bf865e).
Here is the [PR to `brew`](https://github.com/Homebrew/brew/pull/20798) where
this functionality was added back in October of 2025.

View File

@@ -0,0 +1,14 @@
# Open Current Tab In New Window With Vimium
Sometime I have a busy Chrome window going with a bunch of tabs open for
various lines of work as well as a number of tabs that I've neglected to close.
I then open a new tab, find something useful, and realize I'm at a "branching
point". I'm about to start in on a specific chunk of work that will probably
involve opening several more tabs and switch back and forth between some
dashboards. I want to start all of this from a fresh slate -- or at least from
a fresh Chrome window.
With [Vimium](https://github.com/philc/vimium), I can hit `W` (`Shift-w`) to
have the current tab move from the current window to a new window. The original
window, minus that one tab, will be left as is so that I can go back to it as
needed.

View File

@@ -0,0 +1,18 @@
# Monitor Usage Limits From CLI
When I first started using Claude Code enough to push the usage limits, I would
periodically switch over to the browser to check
`https://claude.ai/settings/usage` to see how close I was getting. That page
would tell me what percentage of my allotted usage I had consumed so far for the
current 5-hour session and then how long until that 5-hour usage window resets.
This can also be viewed directly in Claude Code for the CLI.
First, run the `/status` slash command and then _tab_ over to the _Usage_
section. There you will see the same details as in the web view.
I'm also learned, as I write this, that you can go directly to the _Usage_
section by typing the `/usage` slash command.
See [the docs](https://code.claude.com/docs/en/slash-commands) for a listing of
all slash commands.

View File

@@ -0,0 +1,15 @@
# Open Current Prompt In Default Editor
[Claude Code](https://www.claude.com/product/claude-code) gives you a single
line to write a prompt. You can write and write as much as you want, but it will
all be on that single line. And avoid accidentally hitting 'Enter' before you're
done.
I found myself wanting to space out my thoughts, create a code block as part of
a prompt, and generally have a scratch pad instead of just a text box. By
hitting `ctrl-g`, I can move the current prompt into my default editor (in my
case, `nvim`). From there I can continue to write, edit, and format with all the
affordances of an editor.
Once I'm done crafting the prompt, I can save (e.g. `:wq`) and Claude Code will
be primed with that text. I can then hit 'Enter' to let `claude` do its thing.

View File

@@ -0,0 +1,32 @@
# Allow Cursor To Be Launched From CLI
It is nice to be able to open Cursor for a specific project directly from the
terminal like so:
```bash
$ cd ~/dev/my/project
$ cursor .
```
For the `cursor` launcher binary to be available like that, we have to find it
and add it to the path.
It is probably located in the `/Applications` folder and within that nested down
a couple directories is a `bin` directory that contains the binary we're looking
for.
```bash
ls /Applications/Cursor.app/Contents/Resources/app/bin
 bin/
├──  code*
├──  cursor*
└──  cursor-tunnel*
```
The `cursor` binary is what we want, so let's add that to our path. In my case,
I'll add this to my `~/.zshrc` file.
```bash
export PATH="/Applications/Cursor.app/Contents/Resources/app/bin:$PATH"
```

4
dprint.json Normal file
View File

@@ -0,0 +1,4 @@
{
"excludes": ["README.md"],
"plugins": ["https://plugins.dprint.dev/markdown-0.16.0.wasm"]
}

View File

@@ -9,10 +9,10 @@ test runs. Most of these files are tracked (already checked in to the
repository). There are also many new files generated as part of the most recent
test run.
I want to staging the changes to files that are already tracked, but hold off
on doing anything with the new files.
I want to stage the changes to files that are already tracked, but hold off on
doing anything with the new files.
Running `git add spec/cassettes` won't do the track because that will pull in
Running `git add spec/cassettes` won't do the trick because that will pull in
everything. Running `git add --patch spec/cassettes` will take long and be
tedious. Instead what I want is the `-u` flag. It's short for _update_ which
means it will only stage already tracked files.

View File

@@ -0,0 +1,38 @@
# Check If A File Has Changed In A Script
If I'm at the command line and I want to check if a file has changed, I can run
`git diff` and see what has changed. If I want to be more specific, I can run
`git diff README.md` to see if there are changes to that specific file.
If I'm trying to do this check in a script though, I want the command to clearly
tell the script _Yes_ or _No_. Usually a script looks for an exit code to
determine what path to take. But as long as `git diff` runs successfully,
regardless of whether or not their are changes, it is going to have an
affirmative exit code of `0`.
This is why `git diff` offers the `--exit-code` flag.
> Make the program exit with codes similar to diff(1). That is, it exits with 1
> if there were differences and 0 means no differences.
With that in mind, we can wire up a script with `git diff` that takes different
paths depending on whether or not there are changes.
```bash
if ! git diff --exit-code README.md; then
echo "README.md has changes"
else
echo "README.md is clean"
fi
```
We can take this a step further and instead use the `--quiet` flag.
> Disable all output of the program. Implies --exit-code. Disables execution of
> external diff helpers whose exit code is not trusted
This exhibits the same behavior as `--exit-code` and goes the additional step of
silencing diff output and disabling execution of external diff helpers like
`delta`.
See `man git-diff` for more details.

View File

@@ -0,0 +1,36 @@
# Check If A File Is Under Version Control
The `git ls-files` command can be used with the `--error-unmatch` flag to check
if a file is under version control. It does this by checking if any of the
listed files appears on the _index_. If any does not, it is treated as an error.
In a project, I have a `README.md` that is under version control. And I have
`node_modules` that shouldn't be under version control (which is why they are
listed in my `.gitignore` file). I can check the README and a file somewhere in
`node_modules`.
```bash
git ls-files --error-unmatch README.md
README.md
git ls-files --error-unmatch node_modules/@ai-sdk/anthropic/CHANGELOG.md
error: pathspec 'node_modules/@ai-sdk/anthropic/CHANGELOG.md' did not match any file(s) known to git
Did you forget to 'git add'?
```
Notice the second command results in an error because of the untracked
`CHANGELOG.md` file in `node_modules`.
Here is another example of this at work while specifying multiple files:
```bash
git ls-files --error-unmatch README.md node_modules/@ai-sdk/anthropic/CHANGELOG.md package.json
README.md
package.json
error: pathspec 'node_modules/@ai-sdk/anthropic/CHANGELOG.md' did not match any file(s) known to git
Did you forget to 'git add'?
```
Each tracked file gets listed and then the untracked file results in an error.
See `man git-ls-files` for more details.

View File

@@ -0,0 +1,35 @@
# Cherry Pick Multiple Commits At Once
I've always thought of `git cherry-pick` as being a command that you can run
against a single commit by specifying the SHA of that commit. That's how I've
always used it.
The man page for `git-cherry-pick` plainly states:
> Given one or more existing commits, apply the change each one introduces,
> recording a new commit for each.
We can cherry pick multiple commits at once in a single command. They will be
applied one at a time in the order listed.
Here we can see an example of applying two commits to the current branch and
the accompanying output as they are auto-merged.
```bash
$ git cherry-pick 5206af5 6362f41
Auto-merging test/services/event_test.rb
[jb/my-feature-branch 961f3deb] Use the other testing syntax
Date: Fri May 2 10:50:14 2025 -0500
1 file changed, 7 insertions(+), 7 deletions(-)
Auto-merging test/services/event_test.rb
[jb/my-feature-branch b15835d0] Make other changes to the test
Date: Fri May 2 10:54:48 2025 -0500
1 file changed, 7 insertions(+), 7 deletions(-)
```
If the commits cannot be cleanly merged, then you may need to do some manual
resolution as they are applied. Or maybe you want to try including the
`-Xpatience` merge strategy.
See `man git-cherry-pick` for more details. Make sure to look at the _Examples_
section which contains much more advanced examples beyond what is shown above.

View File

@@ -0,0 +1,26 @@
# Clear Entries From Git Stash
I often stash changes as I'm moving between branches, rebasing, or pulling in
changes from the remote. Usually these are changes that I will want to restore
with a `git stash pop` in a few moments.
However, sometimes these stashed changes are abandoned to time.
When I run `git stash list` on an active project, I see that there are nine
entries in the list. When I do `git show stash@{0}` and `git show stash@{1}` to
see the changes that comprise the latest two entries, I don't see anything I
care about.
I can get rid of those individual entries with, say, `git stash drop
stash@{0}`.
But I'm pretty confident that I don't care about any of the nine entries in my
stash list, so I want to _clear_ out all of them. I can do that with:
```bash
$ git stash clear
```
Now when I run `git stash list`, I see nothing.
See `man git-stash` for more details.

View File

@@ -0,0 +1,32 @@
# Exclude A Directory During A Command
Many of the git commands we use, such as `git add`, `git restore`, etc., target
files and paths relative to the current directory. This is typically exactly
what we want, to stage and unstage and so forth the files and directories in
front of us.
I recently ran into a situation where I needed to restore a small subset of
changes. At the same time, I had a massive number of auto-generated files
recording HTTP interactions (hundreds of files, modified on the working tree).
I wanted to run a `git restore`, but wading through all those HTTP recording
files was not feasible.
I needed to exclude those files. They all belonged to a `spec/cassettes`
directory. I could exclude them with a _pathspec_ magic signature pattern which
is used to alter and limit the paths in a git command.
A _pathspec_ magic signature is a special pattern made up of a `:` followed by
some signature declaring what the pattern means.
The `(exclude)`, `!`, and `^` magic signatures all mean the same thing —
exclude. So, we can exclude a directory from a `git restore` command like so:
```bash
$ git restore --patch -- . ':!spec/cassettes'
```
We've employed two pathspec patterns here. The first, `.`, scopes everything to
the current directory. The second, `':!spec/cassettes'` excludes everything in
the `spec/cassettes` directory.
See `man gitglossary` for more on _pathspecs_.

View File

@@ -0,0 +1,44 @@
# Highlight Small Change On Single Line
Sometimes a change gets made on a single, long line of text in a Git tracked
file. If it is a small, subtle change, then it can be hard to pick out when
looking at the diff. A standard diff will show a green line of text stacked on
a red line of text with no more granular information.
There are two ways we can improve the diff output in these situations.
The first is built-in to git. It is the `--word-diff` flag which will visually
isolate the portions of the line that have changed.
```bash
git diff --word-diff README.md
```
Which will produce something like this:
```diff
A collection of concise write-ups on small things I learn [-day to day-]{+day-to-day+} across a
```
The outgoing part is wrapped in `[-...-]` and the incoming part is wrapped in
`{+...+}`.
The second (and my preference) is to use
[`delta`](https://github.com/dandavison/delta) as an external differ and pager
for git.
```bash
git -c core.pager=delta diff README.md
```
I cannot visually demonstrate the difference in a standard code block. So I'll
describe it. We see a red and green line stacked on each other, but with muted
background colors. Then the specific characters that are different stand out
because they are highlighted with brighter red and green. I [posted a visual
here](https://bsky.app/profile/jbranchaud.bsky.social/post/3ln245orlxs2j).
`delta` can also be added as a standard part of your config like I demonstrate
in [Better Diffs With Delta](git/better-diffs-with-delta.md).
h/t to [Dillon Hafer's post on
`--word-diff`](https://til.hashrocket.com/posts/t994rwt3fg-finds-diffs-in-long-line)

View File

@@ -0,0 +1,28 @@
# List All Git Aliases From gitconfig
Running the `git config --list` command will show all of the configuration
settings you have for `git` relative to your current location. Though most of
these setting probably live in `~/.gitconfig`, you may also have some locally
specified ones in `.git/config`. This will grab them all including any `alias`
entries.
We can narrow things down to just `alias` entries using the `--get-regexp` flag.
```bash
$ git config --get-regexp '^alias\.'
alias.ap add --patch
alias.authors shortlog -s -n -e
alias.co checkout
alias.st status
alias.put push origin HEAD
alias.fixup commit --fixup
alias.squash commit --squash
alias.doff reset HEAD^
alias.add-untracked !git status --porcelain | awk '/\?\?/{ print $2 }' | xargs git add
alias.reset-authors commit --amend --reset-author -CHEAD
```
I use `git doff` all the time on feature branches to "pop" the latest commmit
onto the working copy. I was trying to remember exactly what the `git doff`
command is and this was an easy way to check.

View File

@@ -2,7 +2,7 @@
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.
referencing chunks of time further back than 2020.
```bash
git diff --diff-filter=A --name-only HEAD@{2016-02-06}..HEAD@{2017-02-06} -- "*.md"

View File

@@ -0,0 +1,20 @@
# Restore File From One Branch To The Current
On one feature branch I have created some files and made changes to some
existing files as part of spiking a feature. Now I'm on a different branch
taking another shot at it. I want changes from one or two of the files. In the
past I've used `git-checkout` for this task. However, I believe this is one of
the use cases they had in mind when they added `git-restore`.
What I want to do is _restore_ the state of a file as it appears on some source
branch to my current branch. Here is what that looks like:
```bash
$ git restore --source=some-feature-branch app/models/contact.rb
```
Now when I check `git status` I'll see the state of that file on the
`some-feature-branch` branch overlayed on my current working copy. If the file
doesn't exist, it will be created.
See `man git-restore` for more details.

View File

@@ -0,0 +1,56 @@
# Set Up GPG Signing Key
I wanted to have that "Verified" icon start showing up next to my commits in
GitHub. To do that, I need to generate a GPG key, configure the secret key in
GitHub, and then configure the public signing key with my git config.
```bash
# generate a gpg key
$ gpg --full-generate-key
# Pick the following options when prompted
# - Choose "RSA and RSA" (Options 1)
# - Max out key size at 4096
# - Choose expiration date (e.g. 0 for no expiration)
# - Enter "Real name" and "Email"
(I matched those to what is in my global git config)
# - Set passphrase (I had 1password generate a 4-word passphrase)
```
It may take a few seconds to create.
I can see it was created by listing my GPG keys.
```bash
$ gpg --list-secret-keys --keyid-format=long
[keyboxd]
---------
sec rsa4096/1A8656918A8D016B 2025-10-19 [SC]
...
```
I'll need the `1A8656918A8D016B` portion of that response for the next command
and it is what I set as my public signing key in my git config.
First, though, I add the full key block to my GitHub profile which I can copy
like so:
```bash
$ gpg --armor --export 1A8656918A8D016B | pbcopy
```
And then I paste that as a new GPG Key on GitHub under _Settings_ -> _SSH and
GPG Keys_.
Last, I update my global git config with the signing key and the preference to
sign commits:
```bash
git config --global user.signingkey 1A8656918A8D016B
git config --global commit.gpgsign true
```
Without `commit.gpgsign`, I would have to specify the `-S` flag every time I
want to create a signed commit.
[source](https://git-scm.com/book/ms/v2/Git-Tools-Signing-Your-Work)

View File

@@ -0,0 +1,26 @@
# Show Summary Stats For Current Branch
When I push a branch up to GitHub as a PR, there is a part of the UI that shows
you how many lines you've added and removed for this branch. It bases that off
the target branch which is typically your `main` branch.
The `git diff` command can provide those same stats right in the terminal. The
key is to specify the `--shortstat` flag which tells `git` to exclude other diff
output and only show:
- Number of files changed
- Number of insertions
- Number of deletions
Here is the summary stats for a branch I'm working on:
```bash
git diff --shortstat main
8 files changed, 773 insertions(+), 25 deletions(-)
```
We have to be on our feature branch and then we point to the branch (or whatever
ref) we want to diff against. Since I want to know how my feature branch
compares to `main`, I specify that.
See `man git-diff` for more details.

View File

@@ -0,0 +1,25 @@
# Access Your GitHub Profile Photo
Let's say I have my [GitHub profile](https://github.com/jbranchaud) pulled up in
the browser.
```
https://github.com/jbranchaud
```
If I then add `.png` to the end of that in the URL bar:
```
https://github.com/jbranchaud.png
```
I'll be redirected to the URL where the full image file lives. In my case:
```
https://avatars.githubusercontent.com/u/694063?v=4
```
You can pull up yours `https://github.com/<username>.png` to access your profile
image.
[source](https://dev.to/10xlearner/how-to-get-the-profile-picture-of-a-github-account-1d82)

View File

@@ -0,0 +1,19 @@
# Open A PR To An Unforked Repo
Sometimes I will clone a repo to explore the source code or to look into a
potential bug. If my curiosity takes me far enough to make some changes, then I
jump through the hoops of creating a fork, reconfiguring branches, pushing to my
fork, and then opening the branch as a PR against the original repo.
The `gh` CLI allows me to avoid all that hoop-jumping. Directly from the cloned
repo I can use `gh` to create a new PR. It will prompt me to creat a fork. If I
accept, it will seamlessly create it and then open a PR from my fork to the
original.
```bash
$ gh pr create
```
This allows me to create the PR with a few prompts from the CLI. If you prefer,
you can include the `--web` flag to open the PR creation screen directly in the
browser.

View File

@@ -0,0 +1,20 @@
# Target Another Repo When Creating A PR
I have a [`dotfiles` repo](https://github.com/jbranchaud/dotfiles) that I forked
from [`dkarter/dotfiles`](https://github.com/dkarter/dotfiles). I'm adding a
bunch of my own customizations on a `main` branch while continually pulling in
and merging upstream changes.
The primary remote according to `gh` is `jbranchaud/dotfiles`. 98% of the time
that is what I want. However, I occasionally want to share some changes upstream
via a PR. Running `gh pr create` as is will create a PR against my fork. To
override this on a one-off basis, I can use the `--repo` flag.
```bash
$ gh pr create --repo dkarter/dotfiles
```
This will create a PR against `dkarter:master` from my branch (e.g.
[`jbranchaud:jb/fix-hardcoded-paths`](https://github.com/dkarter/dotfiles/pull/373)).
See `man gh-pr-create` for more details.

View File

@@ -0,0 +1,38 @@
# Tell gh What The Default Repo Is
I recently forked [dkarter/dotfiles](https://github.com/dkarter/dotfiles) as a
way of bootstrapping a robust dotfile config for a new machine that I could
start making customizations to. I'm maintaining a `my-dotfiles` branch and keep
things in sync with the original upstream repo.
When trying to go to *my* fork of the repo
([jbranchaud/dotfiles](https://github.com/jbranchaud/dotfiles)) in the web with
the `gh` CLI tool, I ran into a weird issue. It was instead opening up to
`dkarter/dotfiles`.
`gh` was under the wrong impression which repo should be considered the default.
To clarify things for `gh`, there is a command to set the default repo.
```bash
$ gh repo set-default jbranchaud/dotfiles
✓ Set jbranchaud/dotfiles as the default repository for the current directory
```
Now when I run `gh repo view --web`, it opens the browser to my fork of the
dotfiles.
But where does this setting live?
Opening this repo's `.git/config` file I can see a section for the `origin`
remote that includes a new line for `gh-resolved`. This being set to `base`
tells `gh` that this remote is the one to treat as the default repo.
```
[remote "origin"]
url = git@github.com:jbranchaud/dotfiles.git
fetch = +refs/heads/*:refs/remotes/origin/*
gh-resolved = base
```
See `gh repo set-default --help` for more details.

View File

@@ -0,0 +1,34 @@
# Specify Default Team And App For Project
Typically when you run commands with the Heroku CLI you'll need to specify the
name of the app on Heroku you're targeting with the `--app` flag. However, to
first see the names of the apps you may want to run `heroku apps` (or `heroku
list`). That will list the apps for your default team.
If you need to see apps for a different team (i.e. organization), you'll need to
specify that team either with the `--team` flag or by setting that as an
environment variable.
Here I do the latter in an `.envrc` file:
```
# Heroku
export HEROKU_ORGANIZATION=visualmode
```
Once that is set and the environment reloaded, running `heroku apps` will show
the apps specific to that team on Heroku.
Similarly, if you want to set a default app for your project so that you don't
have to always specify the `--app` flag, you can update your `.envrc`
accordingly.
```
# Heroku
export HEROKU_ORGANIZATION=visualmode
export HEROKU_APP=my-app
```
I had a hard time finding official documentation for this which is why I'm
writing this up here. I've manually verified this works with my own team and
app.

View File

@@ -0,0 +1,39 @@
# Allow Number Input To Accept Decimal Values
Here is a number input element:
```html
<input type="number" id="amount" required class="border" />
```
This renders an empty number input box with up and down arrows which will, by
default, increment or decrement the value by **1**.
Of course, I can manually edit the input typing in a value like `1.25`.
However, when I submit that via an HTML form, the submission will be prevented
and the browser will display a validation error.
> Please enter a valid value. The two nearest valid values are 1 and 2.
If I want to be able to input a decimal value like this, I need to change the
`step` value. It defaults to `1`, but I could change it to `2`, `10`, or in
this case to `0.01`.
```html
<input type="number" step="0.01" id="amount" required class="border" />
```
Notice now that as you click the up and down arrows, the value is incremented
and decremented by **0.01** at a time.
If I want to maintain the step value of `1` while allowing decimal values, I
can instead set the `step` value to be `any`.
```html
<input type="number" step="any" id="amount" required class="border" />
```
See the [MDN docs on number
inputs](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/number)
for more details.

View File

@@ -0,0 +1,42 @@
# Format A List Of Items By Locale
The `Intl` module includes a [`ListFormat`
object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat)
which can be used to format a list of items in a consistent way across locales.
I've reinvented the wheel of writing a helper function numerous times across
projects for formatting a list of items that accounts for formatting based on
how many items there are. This built-in function handles that with the added
benefit of working across locales.
Here are lists of three, two, and one items formatted in the `long` styles for
US english.
```javascript
> const formatter = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' });
undefined
> formatter.format(['Alice', 'Bob', 'Carla'])
'Alice, Bob, and Carla'
> formatter.format(['Coffee', 'Tea'])
'Coffee and Tea'
> formatter.format(['Taco'])
'Taco'
```
The difference between `long` and `short` style for a `conjunction` is _and_
versus _&_. In addition to the type`conjunction`, you could also use
`disjunction` which will do an _or_ instead of an _and_. I'm not sure what
you'd use the `unit` type for.
You could use another locale, such as French, as well:
```javascript
> const formatter = new Intl.ListFormat('fr', { style: 'long', type: 'conjunction' });
undefined
> formatter.format(['café', 'thé'])
'café et thé'
```

View File

@@ -0,0 +1,28 @@
# Describe Current Changes And Create New Change
One of the first patterns I learned with `jj` was a pair of commands to
essentially "commit" the working copy and start a fresh, new change. So if I am
done making some changes, I can add a description to the `(no description)`
working copy and then start a new working copy _change_.
```bash
$ jj describe -m "Add status subcommand to show current status"
$ jj new
```
I learned from [Steve](https://steveklabnik.com/) in the [jj
discord](https://discord.gg/dkmfj3aGQN) that a shorthand for this pattern is to
use the `jj commit` command directly.
> When called without path arguments or `--interactive`, `jj commit` is
> equivalent to `jj describe` followed by `jj new`.
That means, instead of the above pair of commands, I could have done:
```bash
$ jj commit -m "Add status subcommand to show current status"
```
That would have had the same result in my case. However, notice the caveats
mentioned in the quote above and check out `man jj-commit` for more details on
that.

View File

@@ -0,0 +1,31 @@
# Squash Changes Into Parent Commit Interactively
While I have some changes in progress as part of the working copy, I can squash
them into the previous / parent commit with the `jj squash` command. Running
that command as is will apply all the working copy changes to the parent leaving
the current revision empty.
I can also interactively squash those changes similar in spirit to how I might
use `git add --patch` to stage and then amend specific changes into the previous
commit with `git`. This can be done with [`jj`](https://github.com/jj-vcs/jj)
using `squash` with the `-i` flag.
```bash
jj squash -i # or --interactive
```
This will open up a TUI where I can click around or use keys. Each file in the
source revision (in my case, the working copy) will be listed. I can move the
cursor between them hitting _space_ to toggle them in or out of the squash
selection.
I can also hit `f` over a given file to toggle _folding_. When folding is on, a
diff of the file will be disclosed with checkboxes for toggling individual
hunks and lines.
Once I'm satisfied with my interactive selection, I can hit `c` to confirm and
only the selected files and changes will be squashed into the parent.
See `man jj-squash` for more details.
[source](https://steveklabnik.github.io/jujutsu-tutorial/real-world-workflows/the-squash-workflow.html)

View File

@@ -0,0 +1,51 @@
# Add A Bunch Of CLI Utilities With coreutils
The [`coreutils`](https://www.gnu.org/software/coreutils/) project is a
collection of useful utilities that every operating system ought to have.
> The GNU Core Utilities are the basic file, shell and text manipulation
> utilities of the GNU operating system. These are the core utilities which are
> expected to exist on every operating system.
While many of these utilities are redundant with BSD utilities that MacOS
chooses to ship with, there are some differences in the overlapping ons and then
many additions from `coreutils`.
They can be installed with Homebrew:
```bash
$ brew install coreutils
```
And then you should have some new things available on your path. Take `shuf`, for
instance. This utility can shuffle and select items from a file or incoming
lines from another command. Here I use it to randomly grab a number between 1
and 5 (with the help of `seq`):
```bash
seq 1 5 | shuf -n 1
3
seq 1 5 | shuf -n 1
2
seq 1 5 | shuf -n 1
5
```
Or how about some utilities for manipulating file names? Among others there is
`realpath`, `basename`, and `dirname`.
```bash
realpath README.md
/Users/lastword/dev/jbranchaud/til/README.md
realpath README.md | xargs basename
README.md
realpath README.md | xargs dirname
/Users/lastword/dev/jbranchaud/til
```
See the [manual](https://www.gnu.org/software/coreutils/manual/coreutils.html)
for many more details.

View File

@@ -0,0 +1,27 @@
# Capture Screenshoot To Clipboard From CLI
MacOS comes with a `screencapture` utility that you can run from the terminal
to activate the built-in screenshot functionality on Mac.
Usually when I am taking a screenshot, I want to do something with it right
away. Such as paste it into an application or group chat. The `-c` flag forces
the screen capture to go the clipboard.
I also generally want to capture a specific area of the screen so that the
captured image includes the right amount of context and nothing more. The `-i`
flag puts you in interactive screen capture mode. That means your cursor will
turn into a crosshair that you can use to make a drag selection of the capture
area.
```bash
$ screencapture -ic
```
Select an area to capture, it's now on your clipboard, paste it where you need
it.
Note: The first time you run this command, your terminal program (e.g. iTerm2)
may prompt you for the necessary OS permissions in order to capture images of
your screen. You'll need to grant those permissions and then rerun the command.
See `man screencapture` for more details.

View File

@@ -0,0 +1,39 @@
# Detect How Long A User Has Been Idle
The `ioreg` utility on MacOS dumps the I/O Kit registry tree. This lets us look
at the state of all hardware devices and drivers registered with I/O Kit.
Looking specifically at the Human Interface Device subsystem (`IOHIDSystem`), we
can find a handful of properties including the `HIDIdleTime`.
```bash
$ ioreg -c IOHIDSystem | awk '/HIDIdleTime/'
| | | "HIDIdleTime" = 91831000
```
That value is the number of nanoseconds since a human input device was last
interacted with. That is the amount of time the user (me) has been idle.
I can convert this to seconds, which is the small amount of time between me
hitting enter in the terminal and the command finding the idle time.
```bash
$ ioreg -c IOHIDSystem | awk '/HIDIdleTime/ {printf "%.2f seconds\n", $NF/1000000000}'
0.13 seconds
```
I can run this in `watch` to see the elapsed idle time increment.
```bash
watch -n 1 "echo -n 'Idle time: '; ioreg -c IOHIDSystem | awk '/HIDIdleTime/ {printf \"%.1f seconds\\n\", \$NF/1000000000}'"
```
After watching the _idle time_ increment for a bit, I can move the mouse and
watch it reset on the next `watch` loop.
This could be used as part of a script that takes certain actions after the user
has been idle for a while, like putting the display to sleep or stopping a time
tracker app.
There is a _lot_ going on in the `ioreg` output and it's hard to make sense of
hardly any of it. I found running `ioreg -c IOHIDSystem | less`, searching for
`IdleTime`, and browsing from there to be a good starting point.

View File

@@ -0,0 +1,41 @@
# Inspect Assertions Preventing Sleep
The `pmset` command is for inspecting and manipulating _Power Management
Settings_ on MacOS. The `-g` flag is for _getting_ details. We can get a summary
of power assertions with `-g assertions`. These assertions are ways that the
system and display are prevented from sleeping.
A common assertion preventing sleep is the user being active. Another example of
an assertion is a program like `caffeinate` that sets a timeout preventing sleep
for a fixed period of time.
Here I activate a 30 minute (1600 second) `caffeinate` session and then I
inspect the power management assertions which shows the details of that
assertion as well as two others.
```bash
caffeinate -t 1600 &
[1] 98217
pmset -g assertions
2025-11-02 13:20:57 -0600
Assertion status system-wide:
BackgroundTask 0
ApplePushServiceTask 0
UserIsActive 1
PreventUserIdleDisplaySleep 0
PreventSystemSleep 0
ExternalMedia 0
PreventUserIdleSystemSleep 1
NetworkClientActive 0
Listed by owning process:
pid 98217(caffeinate): [0x00045477000194b3] 00:00:03 PreventUserIdleSystemSleep named: "caffeinate command-line tool"
Details: caffeinate asserting for 1600 secs
Localized=THE CAFFEINATE TOOL IS PREVENTING SLEEP.
Timeout will fire in 1597 secs Action=TimeoutActionRelease
pid 145(WindowServer): [0x00044f2f00099212] 00:00:00 UserIsActive named: "com.apple.iohideventsystem.queue.tickle serviceID:10009be9e service:AppleUserHIDEventService product:CTRL Keyboard eventType:3"
Timeout will fire in 600 secs Action=TimeoutActionRelease
pid 80(powerd): [0x00044f2f00019216] 00:22:34 PreventUserIdleSystemSleep named: "Powerd - Prevent sleep while display is on"
```
See `man pmset` and `man caffeinate` for more details.

View File

@@ -0,0 +1,36 @@
# Launch Some Confetti
If you have [Raycast](https://www.raycast.com/) installed on your machine, then
you have quick access to some confetti via their quick command palette. Trigger
the command palette to open, start typing `confetti` until it appears as the
focused option, and then hit enter.
🎉
We can launch confetti other ways, including programmatically from scripts.
To do this, we need to first find the _deeplink_ for the Raycast _confetti_
program. Trigger the command palette and type out `confetti` again. However,
this time instead of hitting enter, hit `Cmd+k` to open other actions. Find the
_Copy Deeplink_ option.
You should now have this on your clipboard:
```
raycast://extensions/raycast/raycast/confetti
```
With this deeplink in hand, we can now trigger confetti other places. The
easiest way to do this is to open a terminal and pass that deep link as an
argument to `open`.
```bash
$ open raycast://extensions/raycast/raycast/confetti
```
Now you can wrap that up in any old bash script or even just tack it on to the
end of a run of your test suite:
```bash
$ rails test && open raycast://extensions/raycast/raycast/confetti
```

View File

@@ -0,0 +1,30 @@
# Prevent Sleep With The Caffeinate Command
MacOS has a built-in utility `caffeinate` that can programatically prevent your
machine from sleeping. There are two kinds of sleep that it can prevent via
_assertions_.
> caffeinate creates assertions to alter system sleep behavior.
The two kinds of sleep behavior are _display sleep_ and _system idle sleep_. An
assertion to prevent display sleep can be created with `-d` and system idle
sleep with `-i`.
We can combine those to prevent both and then specify a duration (_timeout_)
with `-t` (with a value in seconds).
```bash
caffeinate -d -i -t 600
```
This creates assertions with 10 minute timeouts for both display and system idle
sleep.
The `caffeinate` command is blocking, so if you want to start it in the
background, you can do that like so:
```bash
caffeinate -d -i -t 600 &
```
See `man caffeinate` for more details.

View File

@@ -0,0 +1,40 @@
# Get Idea Of What Is In A JSON Column
While digging through some data trying to reacquaint myself with the overall
schema and data model, I ran into an issue selecting rows from this
`content_resource` table. There was so much text packed in to the `"body"`,
`"summary"`, and `"description"` key-value pairs of `fields` JSON column that a
simple `select * ... limit 3;` was overwhelming the screen with text and table
formatting characters (i.e. `+------+-------`).
I figured the `fields` JSON followed a reliable structure, at least for records
of the same `type`. So, let's start by only grabbing the
[`json_keys`](https://dev.mysql.com/doc/refman/8.4/en/json-search-functions.html#function_json-keys)
so that I can get a sense of the shape of the JSON.
```sql
select id, json_keys(fields)
from content_resource
where type = 'post'
limit 3;
+-----+-----------------------------------------------------------------------------------------------------------+
| id | json_keys(`fields`) |
+-----+-----------------------------------------------------------------------------------------------------------+
| 1 | ["body", "slug", "state", "title", "summary", "postType", "visibility", "description", "originalLessonId"] |
| 2 | ["body", "slug", "state", "title", "summary", "postType", "visibility", "description", "originalLessonId"] |
| 3 | ["body", "slug", "state", "title", "summary", "postType", "visibility", "description", "originalLessonId"] |
+-----+-----------------------------------------------------------------------------------------------------------+
```
For the `post` type, I see the same keys for this sampling of rows. Now I have
an idea what keys are present and can start digging in further.
My next query might look something like this:
```sql
select id, fields->'$.slug', fields->'$.title', fields->'$.state'
from content_resource
where type = 'post'
limit 3;
```

View File

@@ -0,0 +1,46 @@
# Jump Between Changes In Current File
With the [gitsigns.nvim plugin](https://github.com/lewis6991/gitsigns.nvim) for
Neovim, I get some handy Git-related capabilities like gutter highlighting of
additions, deletions, and changes to lines in the current file. These contiguous
sections of modification to the versioned state of a file are called hunks.
Here are two mappings (in Lua) for gitsigns that allow me to jump to the next
(`]h`) or previous (`[h`) hunk in the current file.
```lua
---@type LazyKeysSpec[]
M.gitsigns_mappings = {
-- Navigation
{
']h',
function()
if vim.wo.diff then
vim.cmd.normal { ']c', bang = true }
else
require('gitsigns').nav_hunk 'next'
end
end,
desc = 'Next Hunk',
},
{
'[h',
function()
if vim.wo.diff then
vim.cmd.normal { '[c', bang = true }
else
require('gitsigns').nav_hunk 'prev'
end
end,
desc = 'Prev Hunk',
},
}
```
This is particularly useful when I've just opened a big file and I want to jump
directly to active changes in that file.
I got this mapping directly from [Dorian's
dotfiles](https://github.com/dkarter/dotfiles).

View File

@@ -0,0 +1,23 @@
# Run nvim With Factory Defaults
Most of the fun of using Neovim is tailoring it to your exact needs with custom
configurations. Your configuration can be made up of environment variables,
`init.lua`/`init.vim`, and user directories on the `runtimepath`.
Perhaps though, you want to load neovim with its "factory defaults". You want
to ignore all your custom config and your _shada_ (shared data) file. I wanted
to do just that recently to verify that neovim has the `ft-manpage` plugin
enabled by default (as opposed to enabled somewhere in the labryinth of my
config files).
The `--clean` flag does just this. It loads built-in plugins, but none of the
user defined config.
```bash
$ nvim --clean
```
This is different than `nvim -u NONE` which excludes all plugins, including
built-in ones.
See `man nvim` and `:help --clean` for more details.

1
notes Submodule

Submodule notes added at 897184eb02

View File

@@ -0,0 +1,23 @@
# See What Databases You Have Access To
Assuming you have the `pscale` CLI installed and you've authenticated with it,
you can run the following to view available databases.
```bash
$ pscale database list
NAME KIND CREATED AT UPDATED AT
----------- ------- ------------- -------------
bookshelf mysql 3 years ago 3 years ago
```
I'm not very active on my personal account. Planetscale is a multi-tenant SaaS
though. I can switch from my personal `org` to another team I have access to.
```bash
$ pscale org switch another-team
```
And then from there I can run `pscale database list` again to see what databases
I have access to from this other organization.
See `pscale database help` and `pscale org help` for more details.

View File

@@ -0,0 +1,39 @@
# Check The Size Of Databases In A Cluster
The `\l` command in `psql` will list all the databases for the server. The
field surfaced by this meta-command are:
- Name
- Owner
- Encoding
- Locale Provider
- Collate
- Ctype
- ICU Locale
- ICU Rules
- Access privileges
If we add a `+`, issuing instead `\l+`, we get three additional fields:
- Size
- Tablespace
- Description
The _Size_ column is the human-formatted size of each database.
Another way to do this is with some SQL querying the underlying record keeping
of the server's database.
```sql
select
db.datname as db_name,
pg_size_pretty(pg_database_size(db.datname)) as db_size
from pg_database db
order by pg_database_size(db.datname) desc;
```
Credit to [this StackOverflow
answer](https://stackoverflow.com/a/18907188/535590) for how to do this with a
SQL query.
[source](https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-META-COMMAND-LIST)

View File

@@ -0,0 +1,58 @@
# Create And Execute SQL Statements With \gexec
The [`\gexec`
meta-command](https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-META-COMMAND-GEXEC)
is a variation of the [`\g`
meta-command](https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-META-COMMAND-G),
both of which can be used in a `psql` session. Whereas the `\g` command sends
the current query in the buffer to the PostgreSQL server for execution, the
`\gexec` command first sends the query to the server for execution and then
executes each row of the result as its own SQL statement.
This is both a bit absurd and powerful. And a bit unnecessary considering all
of the scripting capabilities with anything from bash to any language with a
SQL client library.
Nevertheless, let's take a look at a contrived example of how it works. Here,
we have a SQL statement that does some string concatenation based off values in
an array. This results in three separate `create schema` statements.
```sql
> select
'create schema if not exists schema_' || letter || ';'
from unnest(array['a', 'b', 'c']) as letter
\gexec
CREATE SCHEMA
CREATE SCHEMA
CREATE SCHEMA
> \dn
List of schemas
Name | Owner
----------+-------------------
public | pg_database_owner
schema_a | postgres
schema_b | postgres
schema_c | postgres
(4 rows)
```
Three new schemas get created which we can inspect with `\dn`.
Notice, if we simply execute the primary statement, we can see the intermediate
result that `\gexec` will subsequently execute.
```sql
> select
'create schema if not exists schema_' || letter || ';'
from unnest(array['a', 'b', 'c']) as letter
\g
?column?
---------------------------------------
create schema if not exists schema_a;
create schema if not exists schema_b;
create schema if not exists schema_c;
(3 rows)
```

View File

@@ -0,0 +1,37 @@
# References Target Primary Key By Default
Typically when I am creating a table or adding a column that involves a foreign
key constraint, I explicitly name the reference column.
```sql
create table contacts (
id int generated always as identity primary key,
user_id int references users(id);
);
```
The [Create Table PostgreSQL
Docs](https://www.postgresql.org/docs/17/sql-createtable.html) point out that
specifying the reference column isn't strictly necessary.
> These clauses specify a foreign key constraint, which requires that a group
> of one or more columns of the new table must only contain values that match
> values in the referenced column(s) of some row of the referenced table. If
> the refcolumn list is omitted, the primary key of the reftable is used.
If we're using the primary key as the reference column, then we can choose to
omit the reference column.
```sql
create table contacts (
id int generated always as identity primary key,
user_id int references users;
);
```
In the same way we can do this when adding a column.
```sql
alter table contacts
add column account_id int references account;
```

View File

@@ -0,0 +1,35 @@
# Set Up A Project-Local Cluster With Postgres.app
I want to set up a PostgreSQL cluster in my project directory. This helps
provide some separation and clarity that this cluster and its databases are just
for this project.
This can be done with `Postgres.app` (on Mac) hitting the `+` button in the
bottom left corner of the app. This will pop open a "Create new server" modal.
From there, you'll want to give the server a name that you can identify within
`Postgres.app`. E.g. "<App Name> Cluster"
Then select the Postgres version. My existing project is still on 17, so I'll
select that.
The not so intuitive part is the _Data Directory_. Use the "Choose..." file
picker to find the root directory of your project. Select that. Then click into
the text input for the data directory and append the name of the data directory
_to be created_ to that path. If I want it to all go in `postgres-data`, then my
path will look like:
```
/Users/me/dev/my-app/postgres-data
```
The `postgres-data` directory doesn't exist yet. But it will in a moment.
You probably want the default port, so leave that at `5432` unless you know
otherwise.
Click `Create server`, though that won't actually create the server yet. Now
with that server selected in `Postgres.app` click the `Initialize` button. That
will create the `postgres-data` directory and then run `initdb` under the hood
which will add everything your server needs. It will now be running at that
port, ready to connect.

View File

@@ -0,0 +1,36 @@
# Convert JSON Field To Hash With Indifferent Access
Let's say we have an `Event` model whose backing table includes a `JSONB` (or
`JSON`) field called `details`.
When we access `details` in a Rails context, digging into that nested data we
have to use string keys throughout. However, we may have existing related code
that is dealing with this shape of data using symbol keys. This might put us in
a position where we have to rework a bunch of existing code or do defensive
coding like `details[:user] || details["user"]`.
To avoid that, we can instead have the `Event` model override `details`
converting that underlying data to `HashWithIndifferentAccess` before returning
it.
```ruby
class Event < ApplicationRecord
def details
data = super
return data if data.nil?
case data
when Array
data.map { |item| item.is_a?(Hash) ? item.with_indifferent_access : item }
when Hash
data.with_indifferent_access
else
data
end
end
end
```
With this in place, anywhere in the codebase where we access `details` on an
instance of `Event` we will be able to use string or symbol keys
interchangeably.

View File

@@ -0,0 +1,41 @@
# Customize Template For New Schema Migration
Rails has a set of generator functionality that we can use to scaffold entire
slices of an app all the way down to generating a single migration file.
```bash
$ rails generate migration MakeUserStatusColumnNotNull
```
When we run a migration generator command like that, Rails reaches for the
[baked-in migration
template](https://github.com/rails/rails/blob/92be9af152f721588b7414119c931ea92930947b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt)
and creates a new migration file based on the given name and any other local
variables that get set internally.
That's the standard behavior. However, we can override the migration template
by defining our own template in `lib/templates/migration.rb.tt` of our Rails
app. We'll need to follow the basic structure, but then we can alter it to our
needs.
For instance, I typically like to use the `#up` and `#down` methods and write
raw SQL for my migrations. To help with that this template provides a good
starting point.
```ruby
class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
def up
execute <<~SQL
SQL
end
def down
execute <<~SQL
SQL
end
end
```
We can see in
[`migration_generator.rb`](https://github.com/rails/rails/blob/92be9af152f721588b7414119c931ea92930947b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb#L26-L43)
how locals get set and what template gets chosen.

View File

@@ -0,0 +1,43 @@
# Filter ActiveModel Validation Errors
Now that `ActiveModel` has a custom `Errors` class (as of Rails 6.1) instead of
a hash, we get some useful functionality. Namely, we get a [`#where`
method](https://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-where)
that allows us to filter errors based on the attribute name, type of
validation, and even properties of that validation.
Here I have created a new `Book` without any attributes. All of its validations
are going to fail and we are going to have an `ActiveModel::Errors` object
attached to it with several errors.
```ruby
> book = Book.new
=>
#<Book:0x00000001110397a8
...
> book.valid?
=> false
> book.errors
=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=added_by, type=blank, options={:message=>:required, :if=>#<Proc:0x0000000110096260 /Users/jbranchaud/.asdf/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.2.1/lib/active_record/associations/builder/belongs_to.rb:130 (lambda)>}>, #<ActiveModel::Error attribute=title, type=blank, options={}>, #<ActiveModel::Error attribute=title, type=too_short, options={:count=>3}>, #<ActiveModel::Error attribute=author, type=blank, options={}>, #<ActiveModel::Error attribute=publication_date, type=blank, options={}>]>
```
Let's say I want to check for a specific validation error. I can use `#where`
to filter down by attribute name (e.g. `:title`). I can filter even further by
including the validation type as well (e.g. `:too_short`).
```ruby
> book.errors.where(:title)
=>
[#<ActiveModel::Error attribute=title, type=blank, options={}>,
#<ActiveModel::Error attribute=title, type=too_short, options={:count=>3}>]
> book.errors.where(:title, :too_short)
=> [#<ActiveModel::Error attribute=title, type=too_short, options={:count=>3}>]
> book.errors.where(:title, :too_short).first.message
=> "is too short (minimum is 3 characters)"
> book.errors.where(:title, :too_short).first.full_message
=> "Title is too short (minimum is 3 characters)"
```
This filtering could be used as part of conditional checks for what flash
message gets displayed to the user or even what route/view gets rendered in
response to the error.

View File

@@ -0,0 +1,49 @@
# Format DateTime With Builtin Formats
The Rails [`Date`](https://api.rubyonrails.org/classes/Date.html)/`DateTime`
and [`Time`](https://api.rubyonrails.org/classes/Time.html) classes each come
with a `DATE_FORMATS` constant that is a hash of symbol names to format
strings.
```ruby
> DateTime::DATE_FORMATS
=>
{:short=>"%d %b",
:long=>"%B %d, %Y",
:db=>"%Y-%m-%d",
:inspect=>"%Y-%m-%d",
:number=>"%Y%m%d",
:long_ordinal=>
#<Proc:0x0000000105b2cef0 /Users/jbranchaud/.local/share/mise/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-8.0.1/lib/active_support/core_ext/date/conversions.rb:15 (lambda)>,
:rfc822=>"%d %b %Y",
:rfc2822=>"%d %b %Y",
:iso8601=>
#<Proc:0x0000000105b2cec8 /Users/jbranchaud/.local/share/mise/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-8.0.1/lib/active_support/core_ext/date/conversions.rb:21 (lambda)>}
```
These can be used as a standardized, ready-to-use, named formats when turning
`DateTime` objects into strings.
Here are a few examples
```ruby
> now = DateTime.now
=> Wed, 30 Apr 2025 23:08:08 -0500
> now.to_fs(:long)
=> "April 30, 2025 23:08"
> now.to_fs(:long_ordinal)
=> "April 30th, 2025 23:08"
> now.to_fs(:iso8601)
=> "2025-04-30T23:08:08-05:00"
```
If an unrecognized key is passed to `#to_fs`, then it falls back to the
`:iso8601` format.
```ruby
> now.to_fs(:taco_bell)
=> "2025-04-30T23:08:08-05:00"
```

View File

@@ -0,0 +1,39 @@
# Format Specific html.erb Template Files
There are a few tools out there that can do formatting of `*.html.erb` template
files. One that I like is
[nebulab/erb-formatter](https://github.com/nebulab/erb-formatter#readme)
because it is ready to use in many different editors. That means that it is
easier to adopt on a team of developers with different editor preferences.
That said, there are projects where I don't necessarily want it wired up to run
on save because the formatting changesets will be too agressive. Instead, I
want to run it manually on specific files as I see fit.
To do this, I install the formatter tool:
```bash
$ gem install erb-formatter
```
And now it is available as a CLI tool (try `which erb-format`).
As their docs recommend, I can run it against all files like so:
```
$ erb-format app/views/**/*.html.erb --write
```
If that is too aggressive though, I find it useful to either run against a
specific file:
```
$ erb-format app/views/some/model/index.html.erb --write
```
Or when I'm wrapping up changes on a branch, I like to run it against all the
view files that were touched on this branch:
```
$ git diff --name-only master...HEAD -- app/views | xargs erb-format --write
```

View File

@@ -0,0 +1,55 @@
# Manage Timestamps With Upsert
Modern versions of Rails and ActiveRecord have [an `upsert`
method](https://api.rubyonrails.org/v8.0.2/classes/ActiveRecord/Relation.html#method-i-upsert)
which will, if available, use your database's upsert capability to either
insert a new row or update an existing row based on the unique identifier.
The docs have the following disclaimer:
> It does not instantiate any models nor does it trigger Active Record
> callbacks or validations. Though passed values go through Active Records
> type casting and serialization.
It's a bit different to work with than other ActiveRecord methods. It left me
wondering if it would handle timestamp management or if I would have to do that
myself.
Let's upsert a new record into `books`:
```ruby
> Book.upsert({title: "Shogun", author: "James Clavell", added_by_id: 1, publication_date: Date.today}, unique_by: :id)
=> #<ActiveRecord::Result:0x00000001141c3df0 ...
> Book.select(:id, :title, :created_at, :updated_at).last
=> #<Book:0x0000000113bae898 id: 12, title: "Shogun", created_at: "2025-06-26 14:08:26.035633000 +0000", updated_at: "2025-06-26 14:08:26.035633000 +0000">
```
Notice that the `created_at` and `updated_at` timestamps get set.
Now, let's upsert the record (notice we're passing in the `id`) to update the title with an `ō`.
```ruby
> Book.upsert({id: 12, title: "Shōgun", author: "James Clavell", added_by_id: 1, publication_date: Date.today}, unique_by: :id)
=> #<ActiveRecord::Result:0x0000000113cace98 ...
> Book.select(:id, :title, :created_at, :updated_at).last
=> #<Book:0x00000001143aadd0 id: 12, title: "Shōgun", created_at: "2025-06-26 14:08:26.035633000 +0000", updated_at: "2025-06-26 14:10:46.280480000 +0000">
```
Notice that the `updated_at` gets set to a time about 2 minutes later.
Lastly let's look at the `record_timestamps` option. This is `nil` by default
which means the underlying methods default kicks in which _is_ to record the
timestamps. We can override that behavior by passing in `false`.
```ruby
> Book.upsert({id: 12, title: "Shōgun, Part 2", author: "James Clavell", added_by_id: 1, publication_date: Date.today}, unique_by: :id, record_timestamps: false)
=> #<ActiveRecord::Result:0x0000000113989428 ...
> Book.select(:id, :title, :created_at, :updated_at).last
=> #<Book:0x0000000114fe1b80 id: 12, title: "Shōgun, Part 2", created_at: "2025-06-26 14:08:26.035633000 +0000", updated_at: "2025-06-26 14:10:46.280480000 +0000">
```
Notice that the `updated_at` value doesn't change between this upsert and the
previous upsert.

View File

@@ -0,0 +1,40 @@
# Parameterize A String With Underscores
I have human-readable status strings that I'm working with like `In progress`,
`Pending approval`, and `Completed`. I need to deterministically turn those
into parameterized values that I can compare. That is, I want them lowercased
and separated by underscores instead of spaces.
The `ActiveSupport` `#parameterize` method, as is, gets me pretty close.
```ruby
> statuses = [
"In progress",
"Pending approval",
"Completed"
]
> statuses.map(&:parameterize)
=> [
"in-progress",
"pending-approval",
"completed"
]
```
Those are separated by dashes though. Fortunately, `parameterize` takes a
`separator` option that we can use to verride what character is used to
separate words. Let's use an underscore (`_`).
```ruby
> statuses.map { |str| str.parameterize(separator: '_') }
=> [
"in_progress",
"pending_approval",
"completed"
]
```
See the [`#paramterize`
docs](https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize)
for more details.

View File

@@ -0,0 +1,50 @@
# Prevent Mailer Previews From Cluttering Database
ActionMailer Previews give you a way to view email templates that your system
sends. This is how I check that it is styled properly and that the logic and
data of the template are able to run and render.
Data for a preview typically means we need ActiveRecord objects and even their
associations. If we start creating one-off records in our previews either with
`#create` or with something like `FactoryBot`, those records will get left
behind in our development database. Every view and refresh of a preview will
generate more of these records.
One way to get around that is to use `#new` and `#build`. I've found this
cumbersome and it often leaves assocations missing or inaccessible.
What if instead the preview could clean up after itself? That sounds like a
great job for a database transaction.
Let's create a `test/mailers/previews/base_preview.rb` as a base class for all
our preview classes.
```ruby
class BasePreview < ActionMailer::Preview
def self.call(...)
message = nil
ActiveRecord::Base.transaction do
message = super(...)
raise ActiveRecord::Rollback
end
message
end
end
```
This wraps the existing `self.call` functionality in a transaction that
collects the resulting message from the preview and then rolls back the
database changes.
Now, instead of our individual preview classes inheriting directly from
`ActionMailer::Preview`, they can inherit from `BasePreview`.
```ruby
class UserMailer < BasePreview
# ...
end
```
[source](https://stackoverflow.com/a/31289295)

View File

@@ -0,0 +1,40 @@
# Provide Fake Form Helper To Controllers
I'm rendering a partial from a turbo stream. The partial is meant to be
rendered within a Rails form object because it contains an input element that
needs to reference the form object. The problem is that from the controller
that is streaming the partial, there is no
[FormBuilder](https://api.rubyonrails.org/v6.1.0/classes/ActionView/Helpers/FormBuilder.html)
object.
One way to get around this that I've borrowed from [Justin
Searls](https://justin.searls.co/posts/instantiate-a-custom-rails-formbuilder-without-using-form_with/)
is with a `FauxFormHelper`.
```ruby
module FauxFormHelper
FauxFormObject = Struct.new do
def errors
end
def method_missing(...)
end
def respond_to_missing?(...)
true
end
end
def faux_form
@faux_form ||= ActionView::Helpers::FormBuilder.new(
nil,
FauxFormObject.new,
self,
{}
)
end
end
```
This module defines and exposes a `faux_form` object that controllers and views
can access. Then my partial can recieve that form object as a parameter.

View File

@@ -0,0 +1,46 @@
# Run Dev Processes With Overmind Instead Of Foreman
Most Rails projects that I have worked on have used
[`foreman`](https://github.com/ddollar/foreman) as a development dependency for
running all the processes declared in your Procfile (`Procfile.dev`). As far as
having a single command to run everything (Rails server, asset building,
worker(s), etc.), it does the job.
`foreman` has some serious points of friction though. The one that really stands
out to me is that when I try to debug the development Rails server with
`binding.irb` or `binding.pry`, the other processes tend to interfere.
The alternative to `foreman` that I've been trying out recently is
[`overmind`](https://github.com/DarthSim/overmind). A specific selling point of
`overmind` is that it runs all the development processes in a `tmux` session.
That means you can individually connect to, inspect, and restart each process.
Once you've installed `overmind` (`brew install overmind`), then you can easily
swap it in for `foreman` like so:
```bash
$ overmind start -f Procfile.dev
```
You can connect to any of those processes directly:
```bash
$ overmind connect sidekiq
```
When you want to `binding.irb` the Rails server, you can specifically connect to
the `web` process to do that.
```bash
$ overmind connect web
```
If you need to stop all the process, you can run the `kill` subcommand.
```bash
$ overmind kill
```
Lastly, if you have a `bin/dev` script in your project, it is probably using
`foreman`. If you and your team prefer `overmind`, then update that script
accordingly and you can simply run `bin/dev` going forward.

View File

@@ -0,0 +1,49 @@
# Run Rails Console With Remote Dokku App
Whenever I want to `rails console` into the _staging_ server of an app I'm
working on, I first have to `ssh` into server and then I have to come up with
the [`dokku`](https://dokku.com/) command to run `rails console` against the app
on that server.
```bash
local> ssh app-staging # app-staging is an SSH alias
staging> dokku run my-app rails console
```
I figured out how to reduce the friction of this by collapsing it into a single
command that I can run locally. I can remotely run the `dokku` command with
`ssh` using an interactive session (`-t`).
```bash
local> ssh -t app-staging dokku run my-app rails console
```
That will open up a `rails console` session directly in the current shell
session via a remote SSH connection. The `-t` flag is important because that
makes the session interactive so that I can interact with the REPL.
I've even packaged this up into a bin script (`bin/staging-console`) with a
couple checks to enhance the DX. I won't put the whole thing here, but the gist
of it is:
```bash
#!/usr/bin/env bash
set -e
if [ -z "$DOKKU_STAGING_SSH_ALIAS" ]; then
echo "Error: DOKKU_STAGING_SSH_ALIAS environment variable is not set."
echo ""
# echo more help details here ...
exit 1
fi
# Check if SSH alias exists
# ...
# Check if we can reach the server
# ...
# Run the console
ssh -t "$DOKKU_STAGING_SSH_ALIAS" dokku run my-app rails console "$@"
```

View File

@@ -0,0 +1,55 @@
# Scope Records To A Lower Or Upper Bound
Typically when we use
[`#where`](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-where)
to scope queries against ActiveRecord models, we are looking to do a direct
"equals" comparison.
Such as `auth_codes.user_id = 1` in the example below.
```ruby
> AuthCode.where(user_id: 1)
AuthCode Load (0.4ms) SELECT "auth_codes".* FROM "auth_codes" WHERE "auth_codes"."user_id" = 1 /* loading for pp */ LIMIT 11
```
We can do more powerful things with `#where` (assuming your database supports
it, in my case PostgreSQL), such as comparing over ranges of dates. Ruby's
range syntax gives us an elegant way to express ranges.
```ruby
> 2..10 # range with lower bound of 2 and upper bound of 10
> 2.. # 'end'less range
> ..10 # 'begin'less range
```
These latter two examples are ranges that are unbounded on one side or the
other. We can use these in ActiveRecord `#where` queries to do "greater than or
equal to" and "less than or equal to" conditionals.
And we can do the same with ranges of dates like in the following queries.
```ruby
> AuthCode.where(created_at: 10.days.ago..).count
AuthCode Count (97.1ms) SELECT COUNT(*) FROM "auth_codes" WHERE "auth_codes"."created_at" >= '2025-09-24 00:35:46.937715'
> AuthCode.where(created_at: 10.days.ago..5.days.ago).count
AuthCode Count (0.6ms) SELECT COUNT(*) FROM "auth_codes" WHERE "auth_codes"."created_at" BETWEEN '2025-09-24 00:35:59.901441' AND '2025-09-29 00:35:59.901512'
> AuthCode.where(created_at: ..5.days.ago).count
AuthCode Count (0.3ms) SELECT COUNT(*) FROM "auth_codes" WHERE "auth_codes"."created_at" <= '2025-09-29 00:36:09.731444'
```
Notice in the generated SQL how the simple `#where` method gets transformed
into a `>=`, a `<=`, or a `between` clause.
And while dates are a powerful example of this, there is nothing to stop us
from querying against other kinds of ranges like numeric ones.
```ruby
# Orders under $10
ten_dollars_in_cents = 10 * 100
Order.where.not(fulfilled_at: nil).where(amount: ..ten_dollars_in_cents)
```

View File

@@ -0,0 +1,42 @@
# Use .ruby Extension For Template File
An interesting feature of Rails that I can't seem to find documented anywhere is
that you can write a template file with plain Ruby by using the `.ruby`
extension. For instance, you might want to render some JSON from a template.
Instead of using `jbuilder` or `erb`, you can have a `show.json.ruby` file. This
is also popular with Turbo Stream files -- e.g. `update.turbo_stream.ruby`.
How this works is that the entire file is evaluated as if it were a `.rb` file.
Then the return value of the final statement is what is returned and rendered by
Rails.
```ruby
author_byline = @book.authors.map(&:name).to_sentence
data = {
id: @book.id,
title: @book.title,
author: author_byline,
status: @book.published_at > Time.current ? 'Coming Soon' : 'Published',
publication_year: @book.published_at.year
}
data.to_json
```
That final line converts the hash of data that we've built up into a JSON string
that can then be rendered by the controller action that corresponds to this view
template.
Similarly, you can have a Turbo Stream template `show.turbo_stream.ruby` that
looks something like this:
```ruby
[
turbo_stream.prepend("posts", @post),
turbo_stream.update("form", partial: "form", locals: { post: Post.new })
].join
```
This template file is made up of a single statement which is an array of turbo
stream results that get joined together.

View File

@@ -0,0 +1,39 @@
# Avoid Double Negation With Minitest Refute
As I'm writing some tests for a recent feature, I end up with various test
cases that make a variety of assertions.
```ruby
assert_equal resp_body["id"], expected_id
# ...
assert_not resp_body["items"].empty?
```
The first assertion reads pretty naturally. The second one requires some extra
mental parsing because of the `_not` and then the "negative" check of
`#empty?`.
Another way to express this that might read more naturally is with
[`#refute`](https://ruby-doc.org/stdlib-3.0.2/libdoc/minitest/rdoc/Minitest/Assertions.html#method-i-refute).
```ruby
refute resp_body["items"].empty?
```
This says that we _refute_ that items is empty, so the assertion should fail if
empty.
Ruby is flexible in other ways. We may also prefer to write it as:
```ruby
assert resp_body["items"].present?
```
Or we could even take advantage of a more specific variant of refute with
[`#refute_empty`](https://ruby-doc.org/stdlib-3.0.2/libdoc/minitest/rdoc/Minitest/Assertions.html#method-i-refute_empty):
```ruby
refute_empty resp_body["items"]
```

View File

@@ -0,0 +1,59 @@
# Create A Module Of Utility Functions
In my [latest blog post](https://www.visualmode.dev/create-a-module-of-utility-functions-in-ruby),
I went into full detail about how the [`Module#module_function` method](https://ruby-doc.org/3.4.1/Module.html#method-i-module_function) works.
It creates both a module of utility functions that we can access directly on
that module like we would with `self` methods. It can also be included in a
class as a way of sharing copies of those utility functions with the class. A
key point to them being copies is that they can then be overridden by the
including class.
Here is the example I used in the blog post:
```ruby
module MarkdownHelpers
module_function
def heading(text, level = 1)
("#" * level) + " #{text}"
end
def link(text, href)
"[#{text}](#{href})"
end
def image(alt_text, href)
"!#{link(alt_text, href)}"
end
end
```
I won't cover everything that the blog post covers, but what I found really nice
about this pattern is that I can call those utility functions directly with the
module as the receiver:
```bash
$ ruby -r ./markdown_helpers.rb -e 'puts MarkdownHelpers.link("Click here", "https://example.com")'
[Click here](https://example.com)
```
The alternative to this generally looks like:
```ruby
module MarkdownHelpers
def self.heading(text, level = 1)
("#" * level) + " #{text}"
end
def self.link(text, href)
"[#{text}](#{href})"
end
def self.image(alt_text, href)
"!#{link(alt_text, href)}"
end
end
```
That would be fine, but we completely lose out on the ability to include it as a
mix-in with other classes.

View File

@@ -0,0 +1,85 @@
# Create Mock Class That Can Be Overridden
Let's say I've defined a `MockTwilioClient` class for my system tests so that
they don't have to actually make calls out to the Twilio API.
```ruby
class MockTwilioClient
def send_sms_message
"MSG_SID_123"
end
end
```
Now, any test that is exercising behavior that uses those parts of the
`TwilioClient` can mock those calls in a predictable way.
The above class always is successful when `send_sms_message` is called. What if
we want to simulate an error response? We need a way to override the client for
specific testing scenarios.
Let's create a helper that can create `MockTwilioClient` instances, either as is
or with overrides.
```ruby
def create_mock_twilio_client(&block)
if block_given?
Class.new(MockTwilioClient, &block).new
else
MockTwilioClient.new
end
end
```
If we pass it a block with specific method overrides, it will create a one-off
anonymous subclass of `MockTwilioClient` with that block which effectively
overrides the parent class's methods.
We can put this to use like so:
```ruby
require 'test_helper'
class SomeSystemTest < SystemTestCase
class MockTwilioClient
def send_sms_message
"MSG_SID_123"
end
end
def create_mock_twilio_client(&block)
if block_given?
Class.new(MockTwilioClient, &block).new
else
MockTwilioClient.new
end
end
test "send message to customer" do
mock_client = create_mock_twilio_client
TwilioClient.stub(:new, mock_client) do
# some action that uses `send_sms_message`
# some assertions ...
end
end
test "fail to send message to customer" do
mock_client = create_mock_twilio_client do
def send_sms_message
raise "Failed to send message"
end
end
TwilioClient.stub(:new, mock_client) do
# some action that uses `send_sms_message`
# some assertions ...
end
end
end
```
In the second test case, I override the _success path_ with a version of
`send_sms_message` that raises an error.

View File

@@ -0,0 +1,55 @@
# Decompose Unicode Character With Diacritic Mark
A character like the `ñ` is typically represented by the unicode codepoint of
`U+00F1`. However, it is also possible to represent it with two unicode
codepoints -- the `n` (`U+006E`) and the combining diacritical mark `˜`
(`U+0303`).
We can see that by comparing a typed `ñ` with one where we split it apart into
the separate codepoints. We can do that with
[`#unicode_normalize`](https://apidock.com/ruby/v2_5_5/String/unicode_normalize)
and the `:nfd` argument which stands for _Normalized Form Decomposed_.
```ruby
> "ñ" == "ñ".unicode_normalize(:nfd)
=> false
> "ñ".unicode_normalize(:nfd).length
=> 2
> "ñ".length
=> 1
```
We can inspect the exact codepoints by iterating over each character and
printing out the codepoint value.
```ruby
"ñ".each_char.with_index do |char, i|
puts "#{i}: '#{char}' -> U+#{char.ord.to_s(16).upcase.rjust(4, '0')}"
end
# 0: 'ñ' -> U+00F1
# => "ñ"
"ñ".unicode_normalize(:nfd).each_char.with_index do |char, i|
puts "#{i}: '#{char}' -> U+#{char.ord.to_s(16).upcase.rjust(4, '0')}"
end
# 0: 'n' -> U+006E
# 1: '̃' -> U+0303
#=> "ñ"
```
Notice the difference after the character has been decomposed such that the
diacritic is separated from the character.
This can be done with other characters containing diacritics.
And here we go the other direction with
[`#pack`](https://ruby-doc.org/core-3.0.1/Array.html#method-i-pack).
```ruby
> [0x006E, 0x0303].pack("U*")
=> "ñ"
> [0x00F1].pack("U*")
=> "ñ"
> [0x006E, 0x0303].pack("U*") == [0x00F1].pack("U*")
=> false
```

View File

@@ -0,0 +1,44 @@
# Get Specific Values From Hashes And Arrays
Ruby defines a `#values_at` method on both `Hash` and `Array` that can be used
to grab multiple values from an instance of either of those structures.
Here is an example of grabbing values by key (if they exist) from a hash.
```ruby
> hash = {one: :two, hello: "world", four: 4}
=> {one: :two, hello: "world", four: 4}
> hash.values_at(:one, :four, :three)
=> [:two, 4, nil]
```
And here is an example of grabbing values at specific indexes from an array, if
those indexes exist.
```ruby
> arr = [:a, :b, :c, :d, :e]
=> [:a, :b, :c, :d, :e]
> arr.values_at(0, 3, 6)
=> [:a, :d, nil]
```
Notice that in both cases, `nil` is returned for a key or index that doesn't
exist.
What I like about this method is that in a single call I can grab multiple
named (or indexed) values and get a single array result with those values.
One way I might use this with a JSON response from an API request could look
like this:
```ruby
resp = client.getSomeData(id: 123)
[status, body] = resp.values_at("status", "body")
if status == 200
puts body
end
```
[source](https://docs.ruby-lang.org/en/3.4/Hash.html#method-i-values_at)

View File

@@ -0,0 +1,24 @@
# Get The Names Of The Month
Ruby's `Date` object has a `MONTHNAMES` constant that returns an array of names
of the month. You'd think that means the array contains 12 items. However, the
size of that array is 13.
```ruby
> Date::MONTHNAMES
=> [nil, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
```
Notice it has all 12 months, plus an initial value of `nil`.
This is because it allows us to more intuitive access a month by it's index
without having to do a little subtraction. If I want to know what the 9th month
is, I can do an array access for `9`.
```ruby
> Date::MONTHNAMES[9]
=> "September"
```
Because arrays in Ruby use 0-based indexing, without this baked in `nil` value,
you'd instead get `"October"` when passing in `9`.

View File

@@ -0,0 +1,74 @@
# Install And Require Gems Inline Without Gemfile
[Bundler](https://bundler.io/) has an _inline_ feature where you can declare
gems that should be installed and required for the current file without the use
of a `Gemfile`. This is useful for creating a single-file Ruby script that can
define its own dependencies.
Require `"bundler/inline"` and then add a `gemfile` block toward the top of the
script to specify the source and any gems.
```ruby
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "httparty"
end
```
When the script gets run (e.g. `ruby script.rb`), it will install the gems (if
they haven't already been installed) and then run the script. You can specify
version constraints just like you'd do in a `Gemfile`.
Here is a single-file script using this approach that I wrote to interact with
the Kit API.
```ruby
#!/usr/bin/env ruby
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "httparty"
end
require "json"
require_relative "kit_client"
API_SECRET = ENV["KIT_API_SECRET"]
def fetch_all_tags(api_secret)
client = KitClient.new("https://api.kit.com/v4", api_secret)
tags = []
after_cursor = nil
loop do
params = {per_page: 1000}
params[:after] = after_cursor if after_cursor
response = client.get("/tags", params)
data = JSON.parse(response.body)
tags.concat(data["tags"])
break unless data["pagination"]["has_next_page"]
after_cursor = data["pagination"]["end_cursor"]
end
tags
end
tags = fetch_all_tags(API_SECRET)
tags.each do |tag|
puts tag
end
```
Because I've specified the shebang at the top of the file (and assuming I've
`chmod +x fetch_tags.rb`), I can run this directly as a script with
`./fetch_tags.rb`.
[source](https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html)

View File

@@ -0,0 +1,43 @@
# Join URI Path Parts
The
[`URI.join`](https://ruby-doc.org/stdlib-2.5.1/libdoc/uri/rdoc/URI.html#method-c-join)
method seems like a handy way to combine a base URL with some subpath. However,
there are some subtle gotchas depending on where forward slashes appear in the
two arguments.
Let's first look at the, in my opinion, desired behavior:
```ruby
> URI.join("https://example.com/api/v1/", "users")
=> #<URI::HTTPS https://example.com/api/v1/users>
```
The base URL has a trailing slash and the path that I want to join to it has no
leading slash. The result is a path where `users` is joined to the end of the
base URL. That's what I'm looking for.
Now, let's see some variations on the above approach that give results that I
wasn't expecting and don't want.
```ruby
> URI.join("https://example.com/api/v1", "/users") # 1
=> #<URI::HTTPS https://example.com/users>
> URI.join("https://example.com/api/v1", "users") # 2
=> #<URI::HTTPS https://example.com/api/users>
> URI.join("https://example.com/api/v1/", "/users") # 3
=> #<URI::HTTPS https://example.com/users>
```
1. No trailing slash on the base URL. Leading slash on the path to join. The
path portion of the base URL is wiped out and `/users` is joined in.
2. No trailing slash on the base URL. No leading slash on the path to join. The
`users` path replaces the last part of the path in the base URL.
3. Both a trailing slash in the base URL and a leading slash in the path to
join. Same behavior as 1.
I have two takeaways from this:
- Use with caution. If I'm going to use `URI.join` for this purpose, I need to
be careful to only use the form in the first code block.
- The `URI.join` method is probably meant to be primarily used to join a domain
(e.g. `http://example.com`) that has no path with some path segment.

View File

@@ -0,0 +1,44 @@
# Make Structs Easier To Use With Keyword Initialization
Typically a [`Struct`](https://ruby-doc.org/3.4.1/Struct.html#method-c-new) in
Ruby is defined and initialized like so:
```ruby
> Subscriber = Struct.new(:email, :first_name, :status, :tags)
=> Subscriber
> s1 = Subscriber.new('bob.burgers@example.com', 'Bob', :active, [:food, :family])
=> #<struct Subscriber email="bob.burgers@example.com", first_name="Bob", status=:active, tags=[:food, :family]>
> s1.email
=> "bob.burgers@example.com"
```
That's a nice way to structure light-weight objects.
A potential challenge with multi-argument `Struct` definitions like this,
especially when they aren't colocated with initialization, is that it can be
hard to remember or distinguish the argument order when initializing an instance
of one.
Ruby 2.5 added the `keyword_init` option to help with this exact issue. When
that option is set to `true` for a `Struct` definition, then we get to
initialize it with keyword arguments rather than positional arguments.
```ruby
> Subscriber = Struct.new(:email, :first_name, :status, :tags, keyword_init: true)
=> Subscriber(keyword_init: true)
* s1 = Subscriber.new(
* first_name: 'Bob',
* email: 'bob.burgers@example.com',
* tags: [:food, :family],
* status: :active
> )
=> #<struct Subscriber email="bob.burgers@example.com", first_name="Bob", status=:active, tags=[:food, :family]>
> s1.email
=> "bob.burgers@example.com"
```
Notice I have to use keyword arguments now and that because of that I can
organize them in whatever order makes sense. Coming back to view this line of
code later, it is easy to see attribute each value corresponds to.
[source](https://www.bigbinary.com/blog/ruby-2-5-allows-creating-structs-with-keyword-arguments)

View File

@@ -0,0 +1,30 @@
# Reference Hash Key With Safe Navigation
Let's say we have a variable that we expect to be a hash, but could also be
`nil`. We want to try to grab a value from that hash by referencing a specific
key. Because it could be `nil`, we cannot simply do:
```ruby
stuff[:key]
```
As that could result in `NoMethodError: undefined method '[]' for nil
(NoMethodError)`.
We should use the _safe navigation_ operator (`&`) to avoid raising that error.
However, we should pay attention to a necessary syntax shift from the short-hand
`[:key]` to the long-hand `[](:key)`.
```ruby
stuff&.[](:key)
```
The meaning of this syntax is that we are calling the `#[]` method and we pass
it a single argument `:key` wrapped in parentheses.
Another approach would be to use `#dig` which can feel more ergonomic than the
above syntax switch.
```ruby
stuff&.dig(:key)
```

View File

@@ -0,0 +1,34 @@
# Regenerate Lock File With Newer Bundler
While upgrading to the latest Ruby version (4.0.0), I also wanted to upgrade the
version of `bundler` that my project uses. This shows up at the bottom of the
`Gemfile.lock` file as the `BUNDLED WITH` line. Despite installing the latest
version of `bundler`, I get the following message when I try to install
dependencies.
```bash
$ bundle install
Bundler 4.0.3 is running, but your lockfile was generated with 2.6.2.
Installing Bundler 2.6.2 and restarting using that version.
...
```
Instead, what we need to tell `bundle` to update the locked version of `bundler`
in the `Gemfile.lock`.
```bash
$ bundle update --bundler
Fetching gem metadata from https://rubygems.org/.........
Resolving dependencies...
Bundle updated!
```
The `--bundler` flag for `bundle-update` says the following:
> Update the locked version of bundler to the invoked bundler version.
So we could pass a specific `bundler` version to that flag, but in this case I
want to use the version I'm invoking it with which is the latest that I just
installed.

View File

@@ -0,0 +1,32 @@
# Set Default Tasks For Rake To Run
Let's say our Ruby codebase has a `test` rake task and a `test:system` rake
task. One runs our unit tests and the other runs our system (headless
browser-based) tests. They aren't necessary defined in our `Rakefile`. In fact,
that's how it is in a Rails codebase where these are defined by Rails itself.
We want the default action when [`rake`](https://ruby.github.io/rake/) is
invoked by itself to be to run both of those test tasks.
This can be accomplished by specifying a `default` task and specifying both of
those tasks as prerequisites.
```ruby
task default: ["test", "test:system"]
```
The `default` task itself does nothing. When we invoke it though, it has to run
our prerequisites. So running `rake` results in `test` and then `test:system`
getting run.
If I have something like
[`unicornleap`](https://github.com/dkarter/dotfiles/blob/b5aae6a9edd5766f0cc9100235b0955a9d53aa85/installer/mac-setup.sh#L47-L74)
or
[`confetti`](https://manual.raycast.com/deeplinks#block-702a9613bc82440d853492f553876a20),
then I can have one of those run in the event that all the prerequisites pass.
```ruby
task default: ["test", "test:system"] do
system("unicornleap") if system("which unicornleap > /dev/null 2>&1")
end
```

View File

@@ -0,0 +1,27 @@
# Reference The Full Match In The Replacement
The `&` can be used in the replacement part of a `sed` expression as reference
to the string match for this iteration of the expression. The occurrence of `&`
will be replaced with that entire match.
As the `sed` man page puts it:
> An ampersand (“&”) appearing in the replacement is replaced by the string
> matching the RE.
I made use of this recently with [a `sed` expression that was evaluating a list
of filenames that I wanted to construct into a sequence of `mv`
commands](unix/rename-a-bunch-of-files-by-constructing-mv-commands.md). I needed
the filename that I was matching on to appear as the first argument of the `mv`
command I was constructing.
Here is what that looks like:
```bash
$ ls *.pdf |
sed 's/\(..\)\(..\)\(..\) Statement\.pdf/mv "&" "20\3-\1-\2-statement.pdf"/'
```
Notice right after `mv` in literal quotes is the `&`. That will be replaced in
the resulting replacement with the full matching string of the regular
expression in the first part of the sed statement (`s/<RE>/`).

View File

@@ -0,0 +1,72 @@
# Disable And Enable A Button
With TailwindCSS we can take a couple different approaches to tie the visual
and functional interactivity of a button to another elements state using
classes.
One approach is to use
[`peer`](https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-sibling-state)
and `peer-checked:<class>`.
```html
<div>
<input
type="checkbox"
id="peer-enable"
class="w-5 h-5 cursor-pointer peer"
/>
<label
for="peer-enable"
class="cursor-pointer text-slate-700 font-medium"
>
I agree to the terms and conditions
</label>
<button
class="opacity-40 pointer-events-none grayscale cursor-not-allowed peer-checked:opacity-100 peer-checked:pointer-events-auto peer-checked:grayscale-0 peer-checked:cursor-pointer"
>
Peer Submit
</button>
</div>
```
Classes to make to button appear disabled (e.g. `opacity-40`) as well as
functional classes that affect interactivity (e.g. `pointer-events-none`) are
applied by default. When the sibling checkbox gets checked, the inverted
classes take effect making the button enabled.
The `peer` approach works, but lacks flexibility. As soon as I need to make any
structural changes to the HTML that sever the peer (i.e. sibling) relationship
of the checkbox and the button, those classes stop working.
With
[`group`](https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-the-descendants-of-a-group)
and `group-has-[:checked]:<class>`, I can style the button relative to another
group member as long as everything is nested under some shared group tag.
```html
<div class="group">
<div class="flex items-center gap-3">
<input
type="checkbox"
id="group-enable"
class="w-5 h-5 cursor-pointer"
/>
<label
for="group-enable"
class="cursor-pointer text-slate-700 font-medium"
>
I agree to the terms and conditions
</label>
</div>
<button
class="opacity-40 pointer-events-none grayscale cursor-not-allowed group-has-[:checked]:opacity-100 group-has-[:checked]:pointer-events-auto group-has-[:checked]:grayscale-0 group-has-[:checked]:cursor-pointer"
>
Group Submit
</button>
</div>
```
We can even utilize [named
groups](https://tailwindcss.com/docs/hover-focus-and-other-states#differentiating-nested-groups)
if we have overlapping and conflicting group interactions. But I won't get into
that here.

View File

@@ -0,0 +1,74 @@
# Create Interactive Picker For Set Of Subtasks
For [my TIL repo](https://github.com/jbranchaud/til), I have a `Taskfile.yml`
that defines a set of `notes:*` tasks for interacting with a `NOTES.md` file
that lives in a private Git submodule.
I wanted to make it easier on myself to not have to remember all the different
`notes` subtasks, so I created a helper task to make it easy to see the options
and run one.
A summary of the Taskfile is shown below including the entirety of the `notes`
task. That task will parse a listing of the available tasks (via `task --list`
and some `sed` commands) and pass those to `fzf` to provide an interactive
picker of the available subtasks.
```yaml
tasks:
notes:
desc: Interactive picker for notes tasks
cmds:
- |
TASK=$(task --list | grep "^\* notes:" | sed 's/^\* notes://' | sed 's/\s\+/ - /' | fzf --prompt="Select notes task: " --height=40% --reverse) || true
if [ -n "$TASK" ]; then
TASK_NAME=$(echo "$TASK" | awk '{print $1}' | sed 's/:$//')
task notes:$TASK_NAME
fi
interactive: true
silent: true
notes:edit:
...
notes:sync:
...
notes:open:
...
notes:push:
...
notes:status:
...
notes:pull:
...
notes:diff:
...
notes:log:
...
```
Now I can run the `notes` task to get a summary and interactive picker that
looks like the following:
```sh
task notes
Select notes task:
9/9
> │ Interactive picker for notes tasks
diff: Show uncommitted changes in notes
edit: All-in-one edit, commit, and push notes
log: Show recent commit history for notes
open: Opens NOTES.md (syncs latest changes first) in default editor
pull: Pull latest changes (alias for sync)
push: Commit and push changes to notes submodule
status: Check status of notes submodule
sync: Sync latest changes from the notes submodule
```
It pulls in the subtask name and description. I can then use `fzf`'s navigation
and filtering to narrow down and select the task I want to run.

View File

@@ -0,0 +1,31 @@
# Run A Task If It Meets Criteria
The [Taskfile `status`
directive](https://taskfile.dev/docs/guide#limiting-when-tasks-run) can be used
to tell a task when it needs to run. If it doesn't need to run, it can be
skipped over. The idea being that we're making a status check to see if we're
up-to-date or need to run the task.
For instance, here is a `status` check that determines if there are changes to
commit and push. If there are changes to `NOTES.md`, then we are out-of-date and
need to run the `cmds` that make up the task.
```yaml
notes:push:
desc: Commit and push changes to notes submodule
dir: '{{.NOTES_DIR}}'
cmds:
- git add NOTES.md
- git commit -m "Update notes - $(date '+%Y-%m-%d %H:%M')"
- git push
status:
- git diff --exit-code NOTES.md
silent: false
```
This is useful because I don't want the `git add`, `git commit`, and `git push`
commands to run when there is nothing to do.
Note: this is different from the `preconditions` directive. Instead of
short-circuiting a sequence of tasks, this will either run or skip the task and
move on to the next one.

View File

@@ -0,0 +1,19 @@
# Add Bindings To Split Panes To Current Directory
When I am vertically or horizontally splitting a pane or opening another window,
I generally want it to open to the same directory as I'm currently in. The
default behavior in tmux is for those commands to open to the starting directory
of the session.
Looking through the [tmux.conf in
dkarter/dotfiles](https://github.com/dkarter/dotfiles/blob/master/config/tmux/tmux.conf#L109-L111),
I found a good way to achieve the behavior I want.
```
bind-key - split-window -v -c '#{pane_current_path}'
bind-key \\ split-window -h -c '#{pane_current_path}'
bind-key c new-window -c '#{pane_current_path}'
```
What I like about this is that `-` (vertical) and `\` (horizontal) look visually
like the splits they represent. Meanwhile, I leave `%` and `"` intact.

View File

@@ -0,0 +1,24 @@
# Set Up Forwarding Prefix For Nested Session
I use
[`ctrl-z`](https://github.com/jbranchaud/dotfiles/blob/main/config/tmux/tmux.conf#L57)
(instead of `ctrl-b`) for my tmux prefix key. I also have [`ctrl-z
ctrl-z`](https://github.com/jbranchaud/dotfiles/blob/main/config/tmux/tmux.conf#L138-L139)
configured to toggle back and forth between the previously visited window.
With that in mind, I needed to set up a specific keybinding to send the prefix
key to an inner (nested) tmux session. That's because sometimes, like with a
tool such as `overmind`, you can end up connected to a tmux session while
already within a tmux session.
So, I have `Ctrl-z a` send the prefix key to the inner tmux session. This is
what I added to my
[`tmux.conf`](https://github.com/jbranchaud/dotfiles/blob/main/config/tmux/tmux.conf#L167-L168):
```
# send prefix to inner tmux session (C-z a)
bind-key a send-prefix
```
Simply doing `Ctrl-z d` will detach me from the outer tmux session. If I want to
detach from an inner tmux session, I can use my new keybinding -- `Ctrl-z a d`.

View File

@@ -0,0 +1,32 @@
# Authorize A cURL Request
When making a cURL request to an endpoint that requires authentication,
sometimes you already have a bearer token and other times you have a username
and password pair. If you have a bearer token, you can format a `Authorization`
header with the `-H` flag that includes that value.
If you have a username and password for the API, you can instead use the `-u`
flag. The `-u` flag will format the username and password, base64 encode it,
and then add it as an `Authorization` header.
```bash
$ curl -v -u "username:password" https://some-endpoint.com/api/v1/status
...
> GET /api/v1/status HTTP/2
> Host: some-endpoint.com
> Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQK
> User-Agent: curl/8.1.2
...
```
You can even pass in only the username to the `-u` flag and cURL will know to
prompt you for the password. This is a nice way to avoid putting plain text
passwords in your shell history.
```bash
$ curl -v -u "username" https://some-endpoint.com/api/v1/status
Enter host password for user 'username':
```
See `man curl` for more details.

View File

@@ -0,0 +1,38 @@
# Create A Filename With The Current Date
I was recently working on a script to pull a scrubbed database dump using the
`pg_dump` Postgres utility. Ultimately, the script does something like this to
dump a remote database to a local file:
```bash
pg_dump \
-h host.region.rds.amazonaws.com \
-U db_username \
-d db_name \
-F c \
-f scrubbed-database-$(date +%Y-%m-%d).dump
```
Notice the last part of that command where we define the name of the dump file.
It has a `$(...)` that is used to run and interpolate a command as part of the
filename.
Here is that `date` command run on its own:
```bash
$ date +%Y-%m-%d
2025-04-02
```
In the above command, that would mean if I were to run it today, I'd get
`scrubbed-database-2025-04-02.dump`.
This approach can be used with any command where you are producing a file that
you want to be dated or timestamped.
Here is another example that incorporates the time as well:
```bash
$ touch $(date +%Y%m%d_%H%M%S)-migration.sql
# => 20250402_092442-migration.sql
```

View File

@@ -0,0 +1,29 @@
# Determine ipv4 And ipv6 Public IP Addresses
There are a number of ways to do this. The one that I've settled on is sending a
`curl` request to a public URL that was specifically set up to echo back the
public IP of the device making the request. There are many such URLs, but the
one that I tend to use is `ifconfig.io`.
When I run this as is, I get something like the following which you may
recognize as an _ipv6_ IP address.
```bash
$ curl ifconfig.io
2001:db8:3333:4444:5555:6666:7777:8888
```
This is because if ipv6 is available, like it is for me, `curl` is going to
prefer that.
Now, if I'm trying to track down specifically my ipv4 address, I can use the
`-4` flag (or `--ipv4`).
```bash
$ curl -4 ifconfig.io
73.23.45.157
```
Similarly, I could explicitly specify ipv6 with `-6` or `--ipv6`.
See `man curl` for more details.

View File

@@ -0,0 +1,26 @@
# Exclude A Specific File From fd Results
I recent wrote a
[`cat-to-markdown`](https://github.com/jbranchaud/dotfiles/blob/my-dotfiles/bin/cat-to-markdown)
script that can be piped a list of files from the output of another command to
do its thing. I then used [`fd`](https://github.com/sharkdp/fd) to list a
specific set of files by extension that could be piped to this command.
```bash
fd -e js | cat-to-markdown | pbcopy
```
This worked, but I quickly realized that one of the JavaScript files included in
that listing was massive and didn't need to be included.
To exclude it from the list I can use the `-E` flag and then name the file like
so:
```bash
fd -e js -E super-large-file.js | cat-to-markdown | pbcopy
```
I believe this can be an exact match file path or even a pattern that matches
multiple files.
See `man fd` for more details.

View File

@@ -0,0 +1,39 @@
# Generate A Sequence Of Numbered Items
The `seq` command will output the specified sequence of numbers.
```bash
seq 1 5
1
2
3
4
5
```
With the `-f` (`--format`) flag we can interpolate those numbers as part of a
string.
```bash
seq -f "day_%02g" 1 5
day_01
day_02
day_03
day_04
day_05
```
The `%g` indicates that there is a format specifier for a numeric type which is
where `seq` will inject the current value in the sequence. The `02` indicates
that it should be `0` padded to `2` digits.
We can then pipe this to another unix command, such as `mkdir` in order to
quickly create a bunch of directories for, say, [Advent of Code](https://adventofcode.com/2024).
```bash
mkdir aoc_2024
cd aoc_2024
seq -f "day_%02g" 1 25 | xargs mkdir
```
See `man seq` for more details.

View File

@@ -0,0 +1,60 @@
# Have Script ShellCheck Itself When Executing
The [ShellCheck](https://www.shellcheck.net/) utility can be run against bash
scripts to check if there are any warnings or errors we should fix. It works
great as long as we remember to run it.
I wondered if I could make it easier on myself by not having to remember to run
it. What if my bash script were to `shellcheck` itself?
Here is an example script where at the beginning it looks for and runs the
`shellcheck` utility against `$0` (the path of the script). This is kind of
meta. As the script is executing, it has an external program run against the
entire contents of itself. If there are any `shellcheck` issues, they get
displayed and the program exits early.
```bash
#!/bin/bash
# Exit immediately if any command fails
set -e
# Self-validation using ShellCheck
if command -v shellcheck &> /dev/null; then
echo "Validating script with ShellCheck..."
# $0 refers to the script itself
if ! shellcheck "$0"; then
echo "ShellCheck found issues in the script. Exiting."
exit 1
fi
echo "Script validation passed."
else
echo "Warning: ShellCheck not found. Skipping validation."
fi
echo "Script execution continuing..."
# shellcheck warning here
read -p "Continue with current operation? (yes/no): " CONTINUE_WITH_EXISTING
if [[ ! "$CONTINUE_WITH_EXISTING" =~ ^[Yy][Ee][Ss]$ ]]; then
echo "Operation cancelled."
exit 1
fi
```
This last bit of the script with the `read` command will trigger a warning from
`shellcheck`.
```bash
$ ./check.sh
Validating script with ShellCheck...
In ./check.sh line 23:
read -p "Continue with current operation? (yes/no): " CONTINUE_WITH_EXISTING
^--^ SC2162 (info): read without -r will mangle backslashes.
For more information:
https://www.shellcheck.net/wiki/SC2162 -- read without -r will mangle backs...
ShellCheck found issues in the script. Exiting.
```

View File

@@ -0,0 +1,22 @@
# Interpret Cron Schedule From The CLI
I encounter cron task schedules infrequently enough that I don't remember how
to reliably interpret them at a glance. So when I'm looking over some code and
I see something like `12 2,16 * * *`, I need to translate it to a
human-readable format.
I've typically opened up a browser tab to
[crontab.guru](https://crontab.guru/#12_2,16_*_*_*) for this. However, I did
just learn about a tool for getting the same information from the CLI.
With the [`hcron`](https://github.com/lnquy/cron) binary, I can pass in a
single cron schedule and get the same details right from my terminal.
```bash
$ hcron '12 2,16 * * *'
12 2,16 * * *: At 02:12 AM and 04:12 PM
```
I decided to clone this repo, build the binary from source (it is Go), and then
place the `hcron` binary in my `~/bin` directory which is on my path. Once I
did that, I could start using the command like above.

View File

@@ -0,0 +1,26 @@
# Make Neovim The Default Way To View Man Pages
I was reading the help page for `:Man` which is the built-in plugin to Neovim
for viewing man pages within a Neovim session. In it, they mentioned that the
`MANPAGER` can be set to use Neovim instead of the default man page viewer.
This can be done by setting `MANPAGER` like so:
```bash
$ export MANPAGER='nvim +Man!'
```
After setting this, you can run something like `man git-restore` which will
open the man page for that command in a Neovim session using the Man page
plugin which can do things like follow links to other man pages (`K` or
`Ctrl=]`), quit by hitting `q`, as well as all the motions and search behavior
of Vim.
For long-term use, this can be set in your shell config, e.g. `~/.zshrc`. For
one-off use, you can include it as an env var for a single call to `man`:
```bash
MANPAGER='nvim +Man!' man git-restore
```
See `:h :Man` within a Neovim session for more details.

View File

@@ -0,0 +1,51 @@
# Rename A Bunch Of Files By Constructing mv Commands
I downloaded a bunch of bank statements as PDFs. On the upside they all were
consistently named. On the downside they used an unhelpful date format. With a
date format that puts year before month before day, the files easily sort
alphanumerically. However, these filenames used a date format that put month
before day before year.
Here is a subset of the files
```bash
$ ls *.pdf
'012524 Statement.pdf'
'012725 Statement.pdf'
'022624 Statement.pdf'
'022625 Statement.pdf'
'032524 Statement.pdf'
'032525 Statement.pdf'
```
Notice they are named with `MMDDYY Statement.pdf`. I would instead like for them
to be named as `YYYY-MM-DD-statement.pdf`.
I can generate a series of `mv` statements that then get piped to `sh` which
_evaluates_ them. But first, let's do a dry run of a `sed` statement that
rearranges the date parts.
```bash
$ ls *.pdf | sed 's/\(..\)\(..\)\(..\) Statement\.pdf/mv "&" "20\3-\1-\2-statement.pdf"/'
mv "012524 Statement.pdf" "2024-01-25-statement.pdf"
mv "012725 Statement.pdf" "2025-01-27-statement.pdf"
mv "022624 Statement.pdf" "2024-02-26-statement.pdf"
mv "022625 Statement.pdf" "2025-02-26-statement.pdf"
mv "032524 Statement.pdf" "2024-03-25-statement.pdf"
mv "032525 Statement.pdf" "2025-03-25-statement.pdf"
```
The way this works is that all the `pdf` files in the current directly get
listed out. That gets piped to a `sed` statement that matches on capture groups
against the first three pairs of characters (the date parts) in the filenames.
It matches on the rest of the filename (` Statement.pdf`). This is then replaced
by a `mv `, the full match of the original filename (`&`), and then the new
filename made up of the rearranged date parts.
I can then pipe it to `sh` to run those `mv` commands.
```bash
$ ls *.pdf |
sed 's/\(..\)\(..\)\(..\) Statement\.pdf/mv "&" "20\3-\1-\2-statement.pdf"/' |
sh
```

View File

@@ -0,0 +1,35 @@
# Shorten SSH Commands With Aliases
Each time I need to SSH into one of my remote app boxes, like _staging_, I run a
command like the following:
```bash
$ ssh root@staging-box.com
```
Instead of having to remember the user and domain, I can add aliases to my
`~/.ssh/config` file.
```
# Staging Server
Host staging
HostName staging-box.com
User root
# Sandbox Server
Host sandbox
HostName sandbox-box.com
User root
```
Then I can SSH to each of these like so:
```bash
$ ssh staging
$ ssh sandbox
```
These aliases help a little bit, but they will help even more in situations
where I need other settings configured for the SSH sessions like `Port` or
`ForwardAgent`.

View File

@@ -0,0 +1,33 @@
# Transform Text To Lowercase
I was reading through [`setup.sh` in
dkarter/dotfiles](https://github.com/dkarter/dotfiles/blob/master/setup.sh#L7-L9)
and noticed this function for converting a given bit of text to all lowercase
letters.
```bash
lowercase() {
echo "$1" | sed "y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/"
}
```
It's an interesting use of `sed`, but it made me wonder if `tr` was a better
tool for this job. I looked into it and `tr` is better suited to the task, more
expressive, and also compatible across Mac and Linux.
Here is what it looks like with `tr`:
```bash
lowercase() {
echo "$1" | tr '[:upper:]' '[:lower:]'
}
```
This has the added benefit of working across all kinds of UTF-8 characters.
```bash
$ echo "ΑΛΦΑΒΗΤΟ ΕΛΛΑΔΑ" | tr '[:upper:]' '[:lower:]'
αλφαβητο ελλαδα
```
See `man tr` for more details.

View File

@@ -0,0 +1,30 @@
# Bypass On-Save Tooling When Writing File
Every once in a while I run into an issue where my code formatters or linters
are misconfigured for a project. I try to save a file and it applies formatting
that I don't want. Or in an extreme case, the error ouput of the tool is what
overwrites the file.
I need to troubleshoot my dev tooling eventually, but I don't want to get
sidetracked at the moment. I just want to save the file. What can I do?
Tools like linters and code formatters are typically hooked up to Vim via
autocommands on certain actions like `FileWrite*` or `BufWrite*`. We can
execute a Vim command like writing a file (`w`) while disregarding autocommands
like so:
```vim
:noautocmd w
```
or, write and quit:
```vim
:noautocmd wq
```
This disables all autocommands for this one command. The file gets saved and
the misconfigured formatters and linters don't clobber the changes you
intended.
See `:h noautocmd` for more details.

View File

@@ -0,0 +1,18 @@
# Reword A Commit Message With Fugitive
When you have the fugitive summary buffer (`:Gedit :`) open and there are
unpushed commits, you'll see them listed below the working tree and staging area
details. If you notice an issue with the wording of any of those commits, you
can initiate an interactive rebase to reword the commit from that window.
Navigate the cursor over that commit and then hit `rw` (for _reword_).
This will split open an interactive rebase buffer with `reword <SHA>`. Save that
buffer and the commit message will be opened into a buffer where it can be
amended, just like if you were to amend a commit with an interactive rebase from
the CLI.
The `rw` binding can be used in any fugitive view where commits are listed. For
instance run `:Git log`, navigate to any commit, and then hit `rw`.
See `:h fugitive_r` for details about all the rebase mappings.

View File

@@ -0,0 +1,24 @@
# Open File On Remote Like GitHub
One of my favorite `vim-fugitive` features is being able to run the `:GBrowse`
command to open the current file or current selection of lines to GitHub in the
browser. This is useful for getting a shareable URL or even just being a
starting point for browsing the codebase from GitHub.
VSCode (as well as Cursor) supports this functionality as well. There are a
couple ways to do it.
First, from the File Browser sidebar you can right click on a particular file or
directory. Find the _Open on Remote (Web)_ submenu. From there select _Open File
on Remote_. That will open the file in GitHub with the focused line highlighted
(or will open to that directory, as the case may be).
Alternatively, you can right click in the file editor and follow the exact same
steps as described above.
If you're wanting a permalink to the remote that highlights a selection of
lines, you can highlight that section of lines and right-click the selected
lines and again follow the steps above.
If your remote is some other host like BitBucket, it will recognize that from
your project's gitconfig and open to that instead of GitHub.

View File

@@ -0,0 +1,35 @@
# Do Project Time Tracking From The CLI
The [`watson` CLI utility](https://github.com/jazzband/Watson) is built to help
track time across projects right from the terminal. It is written in Python and
can be installed via `brew` or `pip3`.
To start tracking a new or existing project, I can specify it by name like so:
```bash
$ watson start my-project +reporting
Starting project my-project [reporting] at 00:28
```
This starts a timer for the `my-project` project and includes the tag
`reporting`. Tags are optional but can give some context to what part of a given
project this time session is for.
At any point during a session, we can check the status.
```bash
$ watson status
Project my-project [reporting] started 14 seconds ago (2025.11.14 00:28:31-0600)
```
And when we are done working on that project for the time being, we can stop it.
```bash
$ watson stop
Stopping project my-project [reporting], started a minute ago and stopped just now. (id: e8e8ed5)
```
Once we're ready to start another session (`timeframe`), we just need to run
`watson start ...` again.
See `watson --help` for other subcommands provided by the utility.

View File

@@ -0,0 +1,23 @@
# View Nicely-Formatted CSV In Terminal
I'd just written and run a script to generate a CSV of data requested by a
stakeholder. Before sending it over to them, I wanted to briefly browse through
it to make sure the data passed a spot-check and that the way I structured
things looked reasonable.
There are a dozen ways I can view a CSV file on my machine, but I wasn't
interested in opening another program or navigating to the file in Finder.app.
I had generated the file in the terminal and I wanted to view it there.
The Rust-built [`csvlens`](https://github.com/YS-L/csvlens) CLI is just what I
was looking for.
```bash
$ csvlens data_report_20250627.csv
```
This shows the data spaced out with columns and rows and has a set of
keybindings that can be browsed with `?`.
I got this tool from `brew install csvlens`.