1
0
mirror of https://github.com/jbranchaud/til synced 2026-07-07 01:30:32 +00:00

Compare commits

2 Commits

Author SHA1 Message Date
nick-w-nick 929f7d13ac Merge 295fe153ad into 34ba60d313 2025-03-28 14:11:01 -04:00
nick-w-nick 295fe153ad added mention of ES6 compatibility
Hello, I've added a small blockquote below the description to indicate that this method of accessing an indefinite number of function arguments has been superseded by the use of the spread operator via rest parameters for ES6+ compatibility.
2022-01-06 11:39:04 -05:00
190 changed files with 16 additions and 7532 deletions
-5
View File
@@ -1,5 +0,0 @@
[submodule "notes"]
path = notes
url = git@github.com:jbranchaud/til-notes-private.git
branch = main
ignore = all
+5 -216
View File
@@ -6,16 +6,13 @@ A collection of concise write-ups on small things I learn day to day across a
variety of languages and technologies. These are things that don't really
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
working across different projects via [VisualMode](https://www.visualmode.dev/).
pairing with smart people at Hashrocket.
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
_1811 TILs and counting..._
_1630 TILs and counting..._
See some of the other learning resources I work on:
- [The VisualMode Blog](https://visualmode.dev/blog)
- [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)
@@ -30,10 +27,8 @@ If you've learned something here, support my efforts writing daily TILs by
* [Ansible](#ansible)
* [Astro](#astro)
* [AWS](#aws)
* [Bash](#bash)
* [Brew](#brew)
* [Chrome](#chrome)
* [Claude Code](#claude-code)
* [Clojure](#clojure)
* [CSS](#css)
* [Deno](#deno)
@@ -43,7 +38,6 @@ 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)
@@ -60,7 +54,6 @@ If you've learned something here, support my efforts writing daily TILs by
* [Linux](#linux)
* [LLM](#llm)
* [Mac](#mac)
* [Math](#math)
* [Mise](#mise)
* [MongoDB](#mongodb)
* [MySQL](#mysql)
@@ -87,7 +80,6 @@ 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)
@@ -129,17 +121,10 @@ If you've learned something here, support my efforts writing daily TILs by
- [Turn Off Output Pager For A Command](aws/turn-off-output-pager-for-a-command.md)
- [Use Specific AWS Profile With CLI](aws/use-specific-aws-profile-with-cli.md)
### Bash
- [Edit The Current Command Prompt](bash/edit-the-current-command-prompt.md)
### 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 From Nonstandard Brewfile](brew/install-from-nonstandard-brewfile.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
@@ -150,7 +135,6 @@ 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)
@@ -163,15 +147,6 @@ 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
- [Allow Edits From The Start](claude-code/allow-edits-from-the-start.md)
- [Distinguish Sessions With Different Colors](claude-code/distinguish-sessions-with-different-colors.md)
- [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)
- [Resume Specific Session](claude-code/resume-specific-session.md)
- [Stash The Current Prompt To Send Another First](claude-code/stash-the-current-prompt-to-send-another-first.md)
### Clojure
- [Aggregation Using merge-with](clojure/aggregation-using-merge-with.md)
@@ -225,10 +200,6 @@ 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)
@@ -241,7 +212,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Check For Cached Site Assocation File For iOS](devops/check-for-cached-site-association-file-for-ios.md)
- [Check The Status of All Services](devops/check-the-status-of-all-services.md)
- [Check The Syntax Of nginx Files](devops/check-the-syntax-of-nginx-files.md)
- [Cloudflare Allows CNAME For Apex Domain](devops/cloudflare-allows-cname-for-apex-domain.md)
- [Connect To An RDS PostgreSQL Database](devops/connect-to-an-rds-postgresql-database.md)
- [Default Rails Deploy Script On Hatchbox](devops/default-rails-deploy-script-on-hatchbox.md)
- [Determine The IP Address Of A Domain](devops/determine-the-ip-address-of-a-domain.md)
@@ -251,7 +221,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Reload The nginx Configuration](devops/reload-the-nginx-configuration.md)
- [Resolve The Public IP Of A URL](devops/resolve-the-public-ip-of-a-url.md)
- [Running Out Of inode Space](devops/running-out-of-inode-space.md)
- [Set, Get, And Unset Env Vars With Dokku](devops/set-get-and-unset-env-vars-with-dokku.md)
- [Set Up Domain For Hatchbox Rails App](devops/set-up-domain-for-hatchbox-rails-app.md)
- [SSH Into A Docker Container](devops/ssh-into-a-docker-container.md)
- [SSL Certificates Can Cover Multiple Domains](devops/ssl-certificates-can-cover-multiple-domains.md)
@@ -342,17 +311,13 @@ 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)
@@ -362,14 +327,11 @@ If you've learned something here, support my efforts writing daily TILs by
- [Count Number Of Commits On A Branch](git/count-number-of-commits-on-a-branch.md)
- [Create A New Branch With Git Switch](git/create-a-new-branch-with-git-switch.md)
- [Delete All Untracked Files](git/delete-all-untracked-files.md)
- [Determine Absolute Path Of Top-Level Project Directory](git/determine-absolute-path-of-top-level-project-directory.md)
- [Determine The Hash Id For A Blob](git/determine-the-hash-id-for-a-blob.md)
- [Diffing With Patience](git/diffing-with-patience.md)
- [Display All Git Log Entries In My Local Timezone](git/display-all-git-log-entries-in-my-local-timezone.md)
- [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)
@@ -384,7 +346,6 @@ 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)
@@ -397,7 +358,6 @@ 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)
@@ -409,7 +369,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Move The Latest Commit To A New Branch](git/move-the-latest-commit-to-a-new-branch.md)
- [Override The Global Git Ignore File](git/override-the-global-git-ignore-file.md)
- [Pick Specific Changes To Stash](git/pick-specific-changes-to-stash.md)
- [Programmatically Grab SHA For Head Commit](git/programmatically-grab-sha-for-head-commit.md)
- [Pulling In Changes During An Interactive Rebase](git/pulling-in-changes-during-an-interactive-rebase.md)
- [Push To A Branch On Another Remote](git/push-to-a-branch-on-another-remote.md)
- [Quicker Commit Fixes With The Fixup Flag](git/quicker-commit-fixes-with-the-fixup-flag.md)
@@ -421,12 +380,10 @@ 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)
@@ -434,13 +391,11 @@ 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)
- [Single Key Presses in Interactive Mode](git/single-key-presses-in-interactive-mode.md)
- [Skip A Bad Commit When Bisecting](git/skip-a-bad-commit-when-bisecting.md)
- [Skip Git Hooks As Needed](git/skip-git-hooks-as-needed.md)
- [Skip Pre-Commit Hooks](git/skip-pre-commit-hooks.md)
- [Staging Changes Within Vim](git/staging-changes-within-vim.md)
- [Staging Stashes Interactively](git/staging-stashes-interactively.md)
@@ -452,7 +407,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Transition A Branch From One Base To Another](git/transition-a-branch-from-one-base-to-another.md)
- [Turn Off The Output Pager For One Command](git/turn-off-the-output-pager-for-one-command.md)
- [Two Kinds Of Dotted Range Notation](git/two-kinds-of-dotted-range-notation.md)
- [Undo Latest Changes Committed To Specific File](git/undo-latest-changes-committed-to-specific-file.md)
- [Unstage Changes Wih Git Restore](git/unstage-changes-with-git-restore.md)
- [Untrack A Directory Of Files Without Deleting](git/untrack-a-directory-of-files-without-deleting.md)
- [Untrack A File Without Deleting It](git/untrack-a-file-without-deleting-it.md)
@@ -465,16 +419,6 @@ 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)
- [List PRs Awaiting Your Review](github/list-prs-awaiting-your-review.md)
- [Open A PR To An Unforked Repo](github/open-a-pr-to-an-unforked-repo.md)
- [Open File To Specific Line In Browser](github/open-file-to-specific-line-in-browser.md)
- [Process JSON Output From gh With jq](github/process-json-output-from-gh-with-jq.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)
@@ -522,20 +466,17 @@ If you've learned something here, support my efforts writing daily TILs by
### Heroku
- [Check Ruby Version For Production App](heroku/check-ruby-version-for-production-app.md)
- [Connect To A Database By Color](heroku/connect-to-a-database-by-color.md)
- [Deploy A Review App To A Different Stack](heroku/deploy-a-review-app-to-a-different-stack.md)
- [Diagnose Problems In A Heroku Postgres Database](heroku/diagnose-problems-in-a-heroku-postgres-database.md)
- [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)
@@ -568,7 +509,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Focus The URL Bar](internet/focus-the-url-bar.md)
- [Get Random Images From Unsplash](internet/get-random-images-from-unsplash.md)
- [Grab The RSS Feed For A Substack Blog](internet/grab-the-rss-feed-for-a-substack-blog.md)
- [Hide Overflowing Text For Google Sheets Column](internet/hide-overflowing-text-for-google-sheets-column.md)
- [Search Tweets By Author](internet/search-tweets-by-author.md)
- [Show All Pivotal Stories With Blockers](internet/show-all-pivotal-stories-with-blockers.md)
- [Verify Site Ownership With DNS Record](internet/verify-site-ownership-with-dns-record.md)
@@ -615,7 +555,6 @@ 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)
@@ -625,7 +564,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Get The Response Status From An Axios Error](javascript/get-the-response-status-from-an-axios-error.md)
- [Get The Time Components Of A Date](javascript/get-the-time-components-of-a-date.md)
- [Get The Time Zone Of The Client Computer](javascript/get-the-time-zone-of-the-client-computer.md)
- [Get User's Preferred Language From Browser](javascript/get-users-preferred-language-from-browser.md)
- [Globally Install A Package With Yarn](javascript/globally-install-a-package-with-yarn.md)
- [Globally Install Specific Version Of PNPM](javascript/globally-install-specific-version-of-pnpm.md)
- [Immutable Remove With The Spread Operator](javascript/immutable-remove-with-the-spread-operator.md)
@@ -690,9 +628,7 @@ 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
@@ -726,7 +662,6 @@ If you've learned something here, support my efforts writing daily TILs by
### LLM
- [Count Number Of Tokens In A File](llm/count-number-of-tokens-in-a-file.md)
- [Send cURL To Claude Text Completion API](llm/send-curl-to-claude-text-completion-api.md)
- [Use The llm CLI With Claude Models](llm/use-the-llm-cli-with-claude-models.md)
@@ -735,32 +670,22 @@ 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 Item Layout In Finder Window](mac/clean-up-item-layout-in-finder-window.md)
- [Clean Up Old Homebrew Files](mac/clean-up-old-homebrew-files.md)
- [Control Which Monitor App Switcher Appears On](mac/control-which-monitor-app-switcher-appears-on.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)
- [Read The Lid Angle Sensor For A MacBook](mac/read-the-lid-angle-sensor-for-a-macbook.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)
- [Resizing Both Corners Of A Window](mac/resizing-both-corners-of-a-window.md)
- [Reveal Location Of File In Finder.app](mac/reveal-location-of-file-in-finder-app.md)
- [Run A Hardware Check](mac/run-a-hardware-check.md)
- [Run AppleScript Commands Inline In The Terminal](mac/run-applescript-commands-inline-in-the-terminal.md)
- [Set A Window To Its Default Zoom Level](mac/set-a-window-to-its-default-zoom-level.md)
@@ -772,21 +697,13 @@ If you've learned something here, support my efforts writing daily TILs by
- [View All Windows Of The Current App](mac/view-all-windows-of-the-current-app.md)
- [Write System Clipboard To A File](mac/write-system-clipboard-to-a-file.md)
### Math
- [Generate Permutations Of All Valid 9-ball Racks](math/generate-permutations-of-all-valid-9-ball-racks.md)
### Mise
- [Create Umbrella Task For All Test Tasks](mise/create-umbrella-task-for-all-test-tasks.md)
- [List The Files Being Loaded By Mise](mise/list-the-files-being-loaded-by-mise.md)
- [Look In Ruby Version Dotfile](mise/look-in-ruby-version-dotfile.md)
- [Override Your Project Mise File](mise/override-your-project-mise-file.md)
- [Pick From Tasks Using Interactive Picker](mise/pick-from-tasks-using-interactive-picker.md)
- [Preserve Color Output For Task Command](mise/preserve-color-output-for-task-command.md)
- [Read Existing Dot Env File Into Env Vars](mise/read-existing-dot-env-file-into-env-vars.md)
- [Run A Command With Specific Tool Version](mise/run-a-command-with-specific-tool-version.md)
- [Search Through Bin Paths For Tool Locations](mise/search-through-bin-paths-for-tool-locations.md)
### MongoDB
@@ -805,7 +722,6 @@ 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)
@@ -819,9 +735,7 @@ 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
@@ -859,12 +773,10 @@ 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
- [Avoid Vulnerabilities In New Package Versions](pnpm/avoid-vulnerabilities-in-new-package-versions.md)
- [Execute A Command From The Workspace Root](pnpm/execute-a-command-from-the-workspace-root.md)
- [Install Command Runs For Entire Workspace](pnpm/install-command-runs-for-entire-workspace.md)
- [List The Installed Version Of A Specific Package](pnpm/list-the-installed-version-of-a-specific-package.md)
@@ -887,13 +799,11 @@ 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)
- [Clear The Screen In psql (2)](postgres/clear-the-screen-in-psql-2.md)
- [Compute Hashes With pgcrypto](postgres/compute-hashes-with-pgcrypto.md)
- [Compute Median Instead Of Average](postgres/compute-median-instead-of-average.md)
- [Compute The Levenshtein Distance Of Two Strings](postgres/compute-the-levenshtein-distance-of-two-strings.md)
- [Compute The md5 Hash Of A String](postgres/compute-the-md5-hash-of-a-string.md)
- [Concatenate Strings With A Separator](postgres/concatenate-strings-with-a-separator.md)
@@ -909,7 +819,6 @@ 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)
@@ -993,7 +902,6 @@ 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)
@@ -1004,7 +912,6 @@ 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)
@@ -1061,49 +968,13 @@ If you've learned something here, support my efforts writing daily TILs by
### Python
- [Access Instance Variables](python/access-instance-variables.md)
- [Access Most Recent Return Value In REPL](python/access-most-recent-return-value-in-repl.md)
- [Access Variables Outside Loop Scope](python/access-variables-outside-loop-scope.md)
- [Argument Defaults Are Evaluated When Function Is Defined](python/argument-defaults-are-evaluated-when-function-is-defined.md)
- [Assert Is Only A Development Check](python/assert-is-only-a-development-check.md)
- [Avoid Modification With Frozen Dataclass](python/avoid-modification-with-frozen-dataclass.md)
- [Break Debugger On First Line Of Program](python/break-debugger-on-first-line-of-program.md)
- [Check If Package Is Installed With Pip](python/check-if-package-is-installed-with-pip.md)
- [Check Precondition Before Click Arg Parsing](python/check-precondition-before-click-arg-parsing.md)
- [Control Passing Of Time In Tests](python/control-passing-of-time-in-tests.md)
- [Create A Dummy DataFrame In Pandas](python/create-a-dummy-dataframe-in-pandas.md)
- [Create A Range Of Descending Values](python/create-a-range-of-descending-values.md)
- [Deduplicate A List Into A Tuple](python/deduplicate-a-list-into-a-tuple.md)
- [Define Sequence Of Tests With Parametrize Decorator](python/define-sequence-of-tests-with-parametrize-decorator.md)
- [Define Typed Class Interface With Protocol](python/define-typed-class-interface-with-protocol.md)
- [Dunder Methods](python/dunder-methods.md)
- [Easy Key-Value Aggregates With defaultdict](python/easy-key-value-aggregates-with-defaultdict.md)
- [Enable Pyright Type Checking In Cursor](python/enable-pyright-type-checking-in-cursor.md)
- [Get Absolute Seconds From `timedelta` Object](python/get-absolute-seconds-from-timedelta-object.md)
- [Get Quotient And Remainder In One Operation](python/get-quotient-and-remainder-in-one-operation.md)
- [Install With PIP For Specific Interpreter](python/install-with-pip-for-specific-interpreter.md)
- [Iterate First N Items From Enumerable](python/iterate-first-n-items-from-enumerable.md)
- [Iterate Over A Dictionary](python/iterate-over-a-dictionary.md)
- [Keep A Tally With collections.Counter](python/keep-a-tally-with-collections-counter.md)
- [Lint And Format Project With Ruff](python/lint-and-format-project-with-ruff.md)
- [Load A File Into The Python REPL](python/load-a-file-into-the-python-repl.md)
- [Look Inside Pytest tmp_path](python/look-inside-pytest-tmp-path.md)
- [Make Dataclass Sortable By Specific Field](python/make-dataclass-sortable-by-specific-field.md)
- [Make Secure Temp File For Atomic Write](python/make-secure-temp-file-for-atomic-write.md)
- [Override The Boolean Context Of A Class](python/override-the-boolean-context-of-a-class.md)
- [Parse Relative Time To datetime Object](python/parse-relative-time-to-datetime-object.md)
- [Reclassify Certain Packages As Dev Dependencies](python/reclassify-certain-packages-as-dev-dependencies.md)
- [Set Up Pyright Type Checking In GitHub](python/set-up-pyright-type-checking-in-github.md)
- [Skip Specific Pytest Test Cases](python/skip-specific-pytest-test-cases.md)
- [Sort A List Of Dataclass Instances](python/sort-a-list-of-dataclass-instances.md)
- [Sort Normalized Version Of Data](python/sort-normalized-version-of-data.md)
- [Start The Debugger When A Test Errors](python/start-the-debugger-when-a-test-errors.md)
- [Store And Access Immutable Data In A Tuple](python/store-and-access-immutable-data-in-a-tuple.md)
- [Test A Function With Pytest](python/test-a-function-with-pytest.md)
- [Turn Method Into Cached Property On Class Instance](python/turn-method-into-cached-property-on-class-instance.md)
- [Use pipx To Install End User Apps](python/use-pipx-to-install-end-user-apps.md)
- [Use `__post_init__` For `dataclass` Validations](python/use-post-init-for-dataclass-validations.md)
- [Use Verbose Flag To Get More Diff](python/use-verbose-flag-to-get-more-diff.md)
- [Validate Click Option With Callback](python/validate-click-option-with-callback.md)
### Rails
@@ -1138,18 +1009,14 @@ If you've learned something here, support my efforts writing daily TILs by
- [Cast Common Boolean-Like Values To Booleans](rails/cast-common-boolean-like-values-to-booleans.md)
- [Change The Nullability Of A Column](rails/change-the-nullability-of-a-column.md)
- [Change The Time Zone Offset Of A DateTime Object](rails/change-the-time-zone-offset-of-a-datetime-object.md)
- [Check How Database Is Configured](rails/check-how-database-is-configured.md)
- [Check If ActiveRecord Update Fails](rails/check-if-activerecord-update-fails.md)
- [Check If Any Records Have A Null Value](rails/check-if-any-records-have-a-null-value.md)
- [Check Specific Attributes On ActiveRecord Array](rails/check-specific-attributes-on-activerecord-array.md)
- [Check The Current Named Log Level](rails/check-the-current-named-log-level.md)
- [Clean Up Memory Hungry Rails Console Processes](rails/clean-up-memory-hungry-rails-console-processes.md)
- [Code Statistics For An Application](rails/code-statistics-for-an-application.md)
- [Columns With Default Values Are Nil On Create](rails/columns-with-default-values-are-nil-on-create.md)
- [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)
@@ -1157,9 +1024,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 Conditional Routing Logic In Routes File](rails/define-conditional-routing-logic-in-routes-file.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)
- [Demodulize A Class Name](rails/demodulize-a-class-name.md)
@@ -1176,8 +1041,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [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)
@@ -1190,7 +1053,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Get The Column Names For A Model](rails/get-the-column-names-for-a-model.md)
- [Get The Current Time](rails/get-the-current-time.md)
- [Grab A Random Record From The Database](rails/grab-a-random-record-from-the-database.md)
- [Halt ActionMailer Delivery With Callback](rails/halt-action-mailer-delivery-with-callback.md)
- [Handle Named Arguments In A Rake Task](rails/handle-named-arguments-in-a-rake-task.md)
- [Hash Slicing](rails/hash-slicing.md)
- [Ignore Poltergeist JavaScript Errors](rails/ignore-poltergeist-javascript-errors.md)
@@ -1208,7 +1070,6 @@ 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)
@@ -1217,7 +1078,6 @@ 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)
@@ -1226,9 +1086,7 @@ 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)
@@ -1248,12 +1106,9 @@ 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)
@@ -1282,7 +1137,6 @@ 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)
@@ -1422,9 +1276,8 @@ 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)
- [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)
- [Assoc For Hashes](ruby/assoc-for-hashes.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)
@@ -1444,15 +1297,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)
- [Define A Set Of Class Methods](ruby/define-a-set-of-class-methods.md)
- [Define Multiline Strings With Heredocs](ruby/define-multiline-strings-with-heredocs.md)
- [Destructure The First Item From An Array](ruby/destructure-the-first-item-from-an-array.md)
- [Destructuring Arrays In Blocks](ruby/destructuring-arrays-in-blocks.md)
@@ -1473,7 +1322,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [FactoryGirl Sequences](ruby/factory-girl-sequences.md)
- [Fail](ruby/fail.md)
- [Fetch Warns About Superseding Block Argument](ruby/fetch-warns-about-superseding-block-argument.md)
- [Filter By Type](ruby/filter-by-type.md)
- [Find The Min And Max With A Single Call](ruby/find-the-min-and-max-with-a-single-call.md)
- [Finding The Source of Ruby Methods](ruby/finding-the-source-of-ruby-methods.md)
- [Format A Hash Into A String Template](ruby/format-a-hash-into-a-string-template.md)
@@ -1482,8 +1330,6 @@ 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)
@@ -1491,20 +1337,15 @@ 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)
- [Load A Module And Execute A Statement](ruby/load-a-module-and-execute-a-statement.md)
- [Make A Long String Of Text Readable](ruby/make-a-long-string-of-text-readable.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)
@@ -1531,8 +1372,6 @@ 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)
@@ -1547,7 +1386,6 @@ 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)
@@ -1556,7 +1394,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Single And Double Quoted String Notation](ruby/single-and-double-quoted-string-notation.md)
- [Skip Specific CVEs When Auditing Your Bundle](ruby/skip-specific-cves-when-auditing-your-bundle.md)
- [Skip The Front Of An Array With Drop](ruby/skip-the-front-of-an-array-with-drop.md)
- [Specify Default For Data Definition](ruby/specify-default-for-data-definition.md)
- [Specify Dependencies For A Rake Task](ruby/specify-dependencies-for-a-rake-task.md)
- [Specify How Random Array#sample Is](ruby/specify-how-random-array-sample-is.md)
- [Split A Float Into Its Integer And Decimal](ruby/split-a-float-into-its-integer-and-decimal.md)
@@ -1576,7 +1413,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Update The Gemfile Bundled With Version](ruby/update-the-gemfile-bundled-with-version.md)
- [Use A Case Statement As A Cond Statement](ruby/use-a-case-statement-as-a-cond-statement.md)
- [Use dotenv In A Non-Rails Project](ruby/use-dotenv-in-a-non-rails-project.md)
- [Use Rescue As Part Of Inline Statement](ruby/use-rescue-as-part-of-inline-statement.md)
- [Use Tap For Better Test Data Setup](ruby/use-tap-for-better-test-data-setup.md)
- [Using BCrypt To Create And Check Hashed Passwords](ruby/using-bcrypt-to-create-and-check-hashed-passwords.md)
- [What To Do When You Don't Rescue](ruby/what-to-do-when-you-dont-rescue.md)
@@ -1594,7 +1430,6 @@ 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
@@ -1615,20 +1450,12 @@ 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
- [Add Default Task To List All Tasks](taskfile/add-default-task-to-list-all-tasks.md)
- [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)
@@ -1646,7 +1473,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Kill Other Connections To A Session](tmux/kill-other-connections-to-a-session.md)
- [Kill The Current Session](tmux/kill-the-current-session.md)
- [List All Key Bindings](tmux/list-all-key-bindings.md)
- [List Processes Running Across All Session](tmux/list-processes-running-across-all-sessions.md)
- [List Sessions](tmux/list-sessions.md)
- [Open New Splits To The Current Directory](tmux/open-new-splits-to-the-current-directory.md)
- [Open New Window With A Specific Directory](tmux/open-new-window-with-a-specific-directory.md)
@@ -1659,7 +1485,6 @@ 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)
@@ -1693,9 +1518,6 @@ If you've learned something here, support my efforts writing daily TILs by
### Unix
- [All The Environment Variables](unix/all-the-environment-variables.md)
- [Apply Successive Filters To Lines In Less](unix/apply-successive-filters-to-lines-in-less.md)
- [Authorize A cURL Request](unix/authorize-a-curl-request.md)
- [Browse And Search Help Docs](unix/browse-and-search-help-docs.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)
@@ -1707,7 +1529,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Check The Current Working Directory](unix/check-the-current-working-directory.md)
- [Check The Installed OpenSSL Version](unix/check-the-installed-openssl-version.md)
- [Clear The Screen](unix/clear-the-screen.md)
- [Combine All My TILs Into A Single File](unix/combine-all-my-tils-into-a-single-file.md)
- [Command Line Length Limitations](unix/command-line-length-limitations.md)
- [Compare Two Variables In A Bash Script](unix/compare-two-variables-in-a-bash-script.md)
- [Configure cd To Behave Like pushd In Zsh](unix/configure-cd-to-behave-like-pushd-in-zsh.md)
@@ -1720,18 +1541,13 @@ 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)
- [Deduplicate List While Preserving Original Order](unix/deduplicate-list-while-preserving-original-order.md)
- [Determine ipv4 And ipv6 Public IP Addresses](unix/determine-ipv4-and-ipv6-public-ip-addresses.md)
- [Diff Two Files In Unified Format](unix/diff-two-files-in-unified-format.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)
- [Display Line Numbers While Using Less](unix/display-line-numbers-while-using-less.md)
- [Display The Contents Of A Directory As A Tree](unix/display-the-contents-of-a-directory-as-a-tree.md)
- [Do A Dry Run Of An rsync](unix/do-a-dry-run-of-an-rsync.md)
- [Do Not Overwrite Existing Files](unix/do-not-overwrite-existing-files.md)
@@ -1739,7 +1555,6 @@ 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)
@@ -1757,10 +1572,8 @@ If you've learned something here, support my efforts writing daily TILs by
- [Fix Previous Command With fc](unix/fix-previous-command-with-fc.md)
- [Fix Shim Path After asdf Upgrade](unix/fix-shim-path-after-asdf-upgrade.md)
- [Fix Unlinked Node Binaries With asdf](unix/fix-unlinked-node-binaries-with-asdf.md)
- [Format And Display Small Amounts Of Columnar Data](unix/format-and-display-small-amounts-of-columnar-data.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)
@@ -1775,15 +1588,12 @@ 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)
- [Inspect EXIF Data For An Image File](unix/inspect-exif-data-for-an-image-file.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)
@@ -1806,7 +1616,6 @@ 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)
@@ -1819,16 +1628,13 @@ If you've learned something here, support my efforts writing daily TILs by
- [Print A Range Of Lines For A File With Bat](unix/print-a-range-of-lines-for-a-file-with-bat.md)
- [Print DateTime Represented By Unix Timestamp](unix/print-datetime-represented-by-unix-timestamp.md)
- [Print Milliseconds In Human-Readable Format](unix/print-milliseconds-in-human-readable-format.md)
- [Print Out File With Bat Without Formatting](unix/print-out-file-with-bat-without-formatting.md)
- [Print Out Files In Reverse](unix/print-out-files-in-reverse.md)
- [Print The Current Date In Human-Readable Format](unix/print-the-current-date-in-human-readable-format.md)
- [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)
- [Reverse Each Line Of A File](unix/reverse-each-line-of-a-file.md)
- [Run A Command Repeatedly Several Times](unix/run-a-command-repeatedly-several-times.md)
- [Run A cURL Command Without The Progress Meter](unix/run-a-curl-command-without-the-progress-meter.md)
- [Safely Edit The Sudoers File With Vim](unix/safely-edit-the-sudoers-file-with-vim.md)
@@ -1840,11 +1646,9 @@ 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)
- [Show Tree View Of Processes And Subprocesses](unix/show-tree-view-of-processes-and-subprocesses.md)
- [Skip Paging If Output Fits On Screen With Less](unix/skip-paging-if-output-fits-on-screen-with-less.md)
- [SSH Escape Sequences](unix/ssh-escape-sequences.md)
- [SSH With Port Forwarding](unix/ssh-with-port-forwarding.md)
@@ -1853,16 +1657,13 @@ 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)
- [Unrestrict Where ripgrep Searches](unix/unrestrict-where-ripgrep-searches.md)
- [Update Package Versions Known By asdf Plugin](unix/update-package-versions-known-by-asdf-plugin.md)
- [Use fzf To Change Directories](unix/use-fzf-to-change-directories.md)
- [Use Negative Lookbehind Matching With ripgrep](unix/use-negative-lookbehind-matching-with-ripgrep.md)
- [Use Regex Pattern Matching With Grep](unix/use-regex-pattern-matching-with-grep.md)
- [Use The Readline Keybindings Anywhere](unix/use-the-readline-keybindings-anywhere.md)
- [View A Web Page In The Terminal](unix/view-a-web-page-in-the-terminal.md)
- [View The Source For A Brew Formula](unix/view-the-source-for-a-brew-formula.md)
- [Watch The Difference](unix/watch-the-difference.md)
@@ -1897,7 +1698,6 @@ 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)
@@ -2000,7 +1800,6 @@ 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)
@@ -2049,7 +1848,6 @@ 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)
@@ -2079,7 +1877,6 @@ 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)
@@ -2094,13 +1891,10 @@ If you've learned something here, support my efforts writing daily TILs by
- [Send A Message To A Discord Channel](workflow/send-a-message-to-a-discord-channel.md)
- [Send A PDF To Your Kindle](workflow/send-a-pdf-to-your-kindle.md)
- [Set Recurring Reminders In Slack](workflow/set-recurring-reminders-in-slack.md)
- [Show All Linear Keyboard Shortcuts](workflow/show-all-linear-keyboard-shortcuts.md)
- [Show Linting Errors In Zed](workflow/show-linting-errors-in-zed.md)
- [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 Nicely Formatted Markdown From Terminal](workflow/view-nicely-formatted-markdown-from-terminal.md)
- [View The PR For The Current GitHub Branch](workflow/view-the-pr-for-the-current-github-branch.md)
### XState
@@ -2135,7 +1929,6 @@ If you've learned something here, support my efforts writing daily TILs by
- [Add To The Path Via Path Array](zsh/add-to-the-path-via-path-array.md)
- [Create And Jump Into A Directory](zsh/create-and-jump-into-a-directory.md)
- [Link A Scalar To An Array](zsh/link-a-scalar-to-an-array.md)
- [List Available Zle Keybindings](zsh/list-available-zle-keybindings.md)
- [Use A Space To Exclude Command From History](zsh/use-a-space-to-exclude-command-from-history.md)
## Usage
@@ -2146,10 +1939,6 @@ 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).
@@ -2161,7 +1950,7 @@ I shamelessly stole this idea from
## License
© 2015-2026 Josh Branchaud
© 2015-2025 Josh Branchaud
This repository is licensed under the MIT license. See `LICENSE` for
details.
-103
View File
@@ -1,103 +0,0 @@
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
browse:list:
desc: Print deduped, newest-first TIL paths
silent: true
cmds:
- |
git log --diff-filter=A --name-only --pretty=format: -- '*/*.md' \
| grep -v '^$' \
| awk '!seen[$0]++'
browse:
desc: Pick from 5 most recent TILs (fzf) and open in browser
interactive: true
silent: true
cmds:
- |
FILE=$(task browse:list | head -5 | fzf --prompt="Open TIL: " --height=40% --reverse) || true
if [ -n "$FILE" ]; then
gh browse "$FILE"
fi
browse:latest:
desc: Open the single most recent TIL in the browser
silent: true
cmds:
- |
FILE=$(task browse:list | head -1)
if [ -n "$FILE" ]; then
gh browse "$FILE"
fi
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:
- cd {{.NOTES_DIR}} && git checkout main && git pull
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: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
-18
View File
@@ -1,18 +0,0 @@
# Edit The Current Command Prompt
A neat feature of `bash` is the ability to open whatever the current state of
the command prompt is into your default editor.
Let's say we have a really long command that we've just tried to run, but it
failed and we need to make a small change somewhere in the middle. Instead of
holding the left arrow key for 30 seconds, we can instead hit `CTRL-X CTRL-E`.
This pops us into our `EDITOR` (or maybe `VISUAL`, not sure which). In my case,
that is `nvim`. I now have access to all the features I'm used to in `nvim` for
quickly navigating to and editing, searching and replacing, or whatever.
Once I've got the command how I like it, I can save and exit (`:wq`) and the
updated command will be executed.
This is similar to [the `fc` builtin](unix/fix-previous-command-with-fc.md),
which also happens to be available for `zsh`.
-40
View File
@@ -1,40 +0,0 @@
# 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.
-25
View File
@@ -1,25 +0,0 @@
# Install From Nonstandard Brewfile
When you want to install the packages listed in the `Brewfile` for your current
project (or dotfiles), you can run:
```bash
$ brew bundle
```
And `brew` knows to look for and use the `Brewfile` in the current directory.
If, however, you are trying to run `brew bundle` for a `Brewfile` located
somewhere besides the current directory *OR* you want to target a file with a
non-standard name (like
[`Brewfile.personal`](https://github.com/jbranchaud/dotfiles/blob/main/Brewfile.personal)),
then you can use the `--file` flag.
```bash
$ brew bundle --file Brewfile.personal
```
This is what I do [here in my `dotfiles`
repo](https://github.com/jbranchaud/dotfiles/blob/b053f6251cae7ed52f698fc2a2c40ba82c5881b0/installer/mac-setup.sh#L42-L48).
See `man brew` and find the section on `brew bundle` for more details.
-27
View File
@@ -1,27 +0,0 @@
# 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.
@@ -1,14 +0,0 @@
# 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.
-35
View File
@@ -1,35 +0,0 @@
# Allow Edits From The Start
A common pattern for me when using Claude Code is that I start it up in a
project, I prompt it with a question or feature spec, it either comes up with a
plan or just starts working, and as soon as it is ready to make its first edits
to a file, it prompts me something like:
```
Do you want to make this edit to Taskfile.yml?
1. Yes
2. Yes, allow all edits during this session (shift+tab)
3. Type here to tell Claude what to do differently
```
That's a nice default so that I don't get surprised by Claude Code editing a
bunch of files.
However, if I'm in a git-backed project and I'm going into a session intending
to make edits, then I can skip the formalities. I can tell Claude Code when
starting up the session that edits are allowed.
```sh
$ claude --permission-mode acceptEdits
```
When I do this, I'll see the following indicator below the prompt input field:
```
⏵⏵ accept edits on (shift+tab to cycle)
```
If I've already started `claude` but I forgot to specify that permission mode, I
can also toggle right into _accept edits_ by hitting `Shift+Tab`.
[source](https://www.youtube.com/watch?v=_IK18goX4X8)
@@ -1,24 +0,0 @@
# Distinguish Sessions With Different Colors
I sometimes have several Claude Code sessions open at once. As I bounce between
tmux windows, it can sometimes be tricky to tell them apart at a glance. One way
that Claude Code can help with this is with some light styling. You can change
the accent color of a session with the `/color` command.
Run it as is and it will choose a random color to set the session to.
Or you can pick from any of the available colors which it will give you a hint
for if you type a space after `/color`.
```
/color [red|blue|green|yellow|purple|orange|pink|cyan|default]
```
I can run the following to set it to cyan:
```
/color cyan
```
More details on this kinds of commands can be found in the [_Commands_
docs](https://code.claude.com/docs/en/commands).
@@ -1,18 +0,0 @@
# 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.
@@ -1,15 +0,0 @@
# 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.
-22
View File
@@ -1,22 +0,0 @@
# Resume Specific Session
There are a few different ways to resume a [Claude
Code](https://code.claude.com/docs/en/overview) session.
First, if I have exited a session for the current project and I want to pick
back up with that most recent one, then I can use `claude --continue`.
If I have had a few recent sessions for the current project and I want to
remember what they were and pick up where I left off with one of them, then I
can use `claude --resume` (with no argument). That will open a picker where I
can browser through a summary of the recent sessions based on their starting
prompt. The one I pick is the session that will be resumed.
Finally, if I have grabbed a specific session ID (UUID) during the session from
the `/status` output, then I can reference that value directly.
```sh
$ claude --resume 92170532-be31-4a91-b2a9-025b8fa78232
```
See `claude --help` for more details.
@@ -1,25 +0,0 @@
# Stash The Current Prompt To Send Another First
I've been working my way through the current cohort of Matt Pocock's [Claude
Code for Real
Engineers](https://www.aihero.dev/cohorts/claude-code-for-real-engineers-2026-04).
The best part about going through a series of videos like this is being able to
pick up big and small tips and tricks from another person's workflow.
One of the small things I picked up in an early video is the ability to stash
the current prompt.
Let's say I've gone to the trouble of writing out a detailed prompt, `@`'ing
some files, and so forth. Then I realize I need first prompt Claude to do
something else first. Instead of copy-pasting that prompt into my notes,
deleting it, issuing a different prompt, and then pasting it back in, I can hit
`Ctrl-s`.
`Ctrl-s` will _stash_ the current prompt, clearing out the prompt input. I can
then type in something else. Once I hit enter for that new prompt, it will be
sent to Claude and the stashed prompt will be immediately populated back into
the input.
Though `Ctrl-s` is mentioned when you hit `?` from within `claude` session, I
don't see it documented anywhere in their [Interactive Mode
reference](https://code.claude.com/docs/en/interactive-mode).
@@ -1,32 +0,0 @@
# 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"
```
@@ -1,19 +0,0 @@
# Cloudflare Allows CNAME For Apex Domain
If you want to set up a custom root (apex) domain with an app hosting provider
[like
Heroku](https://devcenter.heroku.com/articles/custom-domains#add-a-custom-root-domain),
you're going to need to work with a DNS provider that supports the non-standard
`ALIAS` records (or something equivalent).
In my case, I have my domain registered with Cloudflare. Cloudflare supports
this kind of CNAME lookup of an apex domain through [_CNAME
flattening_](https://developers.cloudflare.com/dns/cname-flattening/).
Unlike other registrars that use a separate `ALIAS` record concept, Cloudflare
allows you to set up a specialized `CNAME` record. Go into the DNS settings for
the domain of interest, click "Add Record", and then select `CNAME`. From there,
instead of entering a traditional subdomain like `www`, you put the `@` symbol
which tells Cloudflare that this is a record for the apex domain. That record
will still point to a target like `abc123.herokudns.com` as a more traditional
`CANME` would do.
@@ -1,29 +0,0 @@
# Set, Get, And Unset Env Vars With Dokku
The `dokku` CLI provides `config` subcommands for managing environment variables
for the target container.
An env var can be set for an active container with `config:set`:
```bash
$ dokku config:set app-name JEMALLOC_ENABLED=true MALLOC_CONF="stats_print:true"
```
Notice I'm able to set multiple env vars at once if needed.
If I ever need to check what an env var is currently set to for one of my app
containers, I can use `config:get`:
```bash
$ dokku config:get app-name JEMALLOC_ENABLED
true
```
I can always override any value with another `config:set`. However, if I need to
entirely remove the env var, I can use `config:unset`:
```bash
$ dokku config:unset app-name MALLOC_CONF
```
[source](https://dokku.com/docs/configuration/environment-variables/)
-4
View File
@@ -1,4 +0,0 @@
{
"excludes": ["README.md"],
"plugins": ["https://plugins.dprint.dev/markdown-0.16.0.wasm"]
}
@@ -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 stage the changes to files that are already tracked, but hold off on
doing anything with the new files.
I want to staging 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 trick because that will pull in
Running `git add spec/cassettes` won't do the track 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.
@@ -1,38 +0,0 @@
# 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.
@@ -1,36 +0,0 @@
# 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.
@@ -1,35 +0,0 @@
# 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.
-26
View File
@@ -1,26 +0,0 @@
# 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.
@@ -1,39 +0,0 @@
# Determine Absolute Path Of Top-Level Project Directory
The `git rev-parse` command is a git plumbing command for parsing different
kinds of things in git into a canonical form that can be used in a deterministic
way by scripts. I would typically think of using it to work with branch names,
tags, and other kinds of refs.
There is a handy, sorta off-label use for it in determining the absolute path of
the root directory for the current git repository. Use the `--show-toplevel`
flag with no other arguments.
```bash
git rev-parse --show-toplevel
/Users/lastword/dev/jbranchaud/til
```
Here, I am in the local copy of [my TIL repo](https://github.com/jbranchaud/til). This command gives me the absolute
path of the top-level directory where that `.git` directory resides.
This is useful for scripts that need to orient themselves to the current
project's top-level directory regardless of what directory they are being
executed from. This is useful for things like a git hook script or monorepos
with scripts located in a specific sub-project directory.
Also worth mentioning is the `--show-superproject-working-tree` flag. In my TIL
repo, I have a private repository included as a submodule. Within that directory
`--show-toplevel` will produce the absolute path to the submodule. If I instead
want the absolute path of the _super project_ (in this case TIL), then I can use
this other flag.
```bash
git rev-parse --show-toplevel
/Users/lastword/dev/jbranchaud/til/notes
git rev-parse --show-superproject-working-tree
/Users/lastword/dev/jbranchaud/til
```
See `man git-rev-parse` for more details.
@@ -1,30 +0,0 @@
# Display All Git Log Entries In My Local Timezone
I tend to work with remote teams distributed across across multiple time zones.
In that context, it is important to have an awareness of what time zone each
person is operating in and to communicate clearly around that.
When looking at the output for `git log` on a distributed team, the timestamps
for each entry can be all over the place. If I want to understand when something
was committed, I have to look at the time as well as the time zone offset and
mentally translate it to my own time zone.
There is a `git config` option to alleviate this issue by having `git log`
convert and display all timestamps into your local time zone.
```bash
$ git config --global log.date rfc-local
```
Running that will add this entry to your _global_ git config file:
```
[log]
date = rfc-local
```
Now the time that was displaying as `Wed Apr 8 20:12:33 2026 -0400` will display
as `Wed, 8 Apr 2026 19:12:33 -0500`.
This also helps with smoothing out differences from DST and for commits produced
by AI agents in sandbox environments where the locale is set to UTC.
@@ -1,32 +0,0 @@
# 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_.
@@ -1,44 +0,0 @@
# 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)
@@ -1,28 +0,0 @@
# 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.
@@ -1,33 +0,0 @@
# Programmatically Grab SHA For Head Commit
When I use `gh browse path/to/some-file.txt`, it opens the browser to that file
in GitHub. However, it targets the default branch (`main`) by default which is
not very useful as a permalink because what that file looks like on `main` is
liable to change.
There is a `--commit` flag you can use to have it instead open to that file at a
specific commit SHA.
So what SHA do I pass as an argument to that flag?
Often what I would like to grab is a reference to the current version of the
file which is whatever it looks like for the `HEAD` commit. But `HEAD` is
another moving target reference. The `git rev-parse` command can translate
`HEAD` into a specific SHA though.
```bash
git rev-parse --short HEAD
3402428
git rev-parse HEAD
3402428aadc02cfdc9825c8feb593443e72f50cd
```
Either of those will work. I can use a bash command substitution then to tie it
all together into a single command:
```bash
gh browse path/to/some-file.txt --commit=$(git rev-parse --short HEAD)
```
See `man git-rev-parse` for more details.
@@ -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 chunks of time further back than 2020.
referencing chuncks of time further back than 2020.
```bash
git diff --diff-filter=A --name-only HEAD@{2016-02-06}..HEAD@{2017-02-06} -- "*.md"
@@ -1,20 +0,0 @@
# 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.
-56
View File
@@ -1,56 +0,0 @@
# 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)
@@ -1,26 +0,0 @@
# 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.
-33
View File
@@ -1,33 +0,0 @@
# Skip Git Hooks As Needed
Projects have Git hooks configured for all sorts of reasons. Most common are
`pre-commit` hooks which verify certain aspects of the contents of a commit.
A `pre-commit` hook could check that the tests all pass, that the changes don't
include any debugging statements, and so forth. There are all kinds of hooks
though, like `pre-rebase` and `post-checkout`.
These hooks can sometimes get in the way and we may need to skip or disable them
on a one-off basis.
Several Git commands offer a `--no-verify` flag which can skip running the hook
associated with that command.
- `git commit --no-verify` (skips `pre-commit` and `commit-msg` hooks)
- `git push --no-verify` (skips `pre-push` hook)
- `git merge --no-verify` (skips `pre-merge-commit` hook)
- `git am --no-verify` (skips `applypatch-msg` and `pre-applypatch` hooks)
If you look in the `.git/hooks` directory, there are several other hooks not
covered by the above. So, what if I am doing an action like `git checkout` and I
want to skip the `post-checkout` hook?
I can override the `hooksPath` config for that one command with the `-c` flag.
```sh
$ git -c core.hooksPath=/dev/null checkout ...
```
By setting it to `/dev/null`, it will find *no* hooks available, so none will be
executed for this command.
See `man git-config` for more details on `core.hooksPath`.
@@ -1,36 +0,0 @@
# Undo Latest Changes Committed To Specific File
I'm reviewing the changes I've made in a PR before I request a review from my
team. There are a scattering of changes in one file that I've changed my mind
on. Everything else looks good though. So, I need to undo the changes in that
file before proceeding.
Manually undoing them is going to be clunky. There is a way to do it with `git
checkout`, but that is one of the ways in which `git-checkout` was overloaded
leading to the release of `git-restore`.
Let's use `git-restore` instead. By specifying a `--source`, I can tell `git`
what _ref_ in the commit history that file should be restored to. I'm on a
short-lived feature branch, so pointing to `main` is good enough.
```bash
$ git restore --source=main app/models/customer.rb
```
If I've changed a file at multiple points on this feature branch and I don't
want to undo all of them, then pointing to `main` is no longer going to work.
Instead, I can point to the commit right before the current one (`HEAD`) that
I'm trying to undo.
```bash
$ git restore --source=HEAD~ app/models/customer.rb
```
This really isn't much different than the `git-checkout` version, but I still
find it to be a little clearer.
```bash
$ git checkout HEAD~ -- app/models/customer.rb
```
See `man git-restore` for more details.
@@ -1,25 +0,0 @@
# 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)
-31
View File
@@ -1,31 +0,0 @@
# List PRs Awaiting Your Review
If you work on a software team or steward an open-source project, then there are
likely some open PRs that you've been tagged to review. I am usually able to
catch most review requests as they come up either from the GitHub email
notifications or by keeping an eye on the PRs tab of active projects. Sometimes
I get consumed by a task and something slips through the cracks.
There are a couple other ways to quickly check if anything is waiting on my
review.
From the web UI I can visit the following URL which will show all PRs across all
projects where my review has been requested:
[https://github.com/pulls/review-requested](https://github.com/pulls/review-requested)
The GitHub CLI (`gh`) can do the same and I can do it right from the terminal
instead of navigating several clicks within GitHub's web UI.
```bash
$ gh search prs --review-requested=@me --state=open
```
That too will list PRs across all projects that are open and awaiting my review.
If that one ends up being a little too noisy, you can also use `gh` to _list_
just PRs for the current project:
```bash
$ gh pr list --search "review-requested:@me"
```
-19
View File
@@ -1,19 +0,0 @@
# 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.
@@ -1,46 +0,0 @@
# Open File To Specific Line In Browser
Often one of the best ways to point a teammate to a line of code is to share a
GitHub link to a specific file and line number. Sometimes even a specific
commit.
For the longest time I would manually open GitHub, navigate to that file, and so
forth. The `gh` CLI supports this with the `browse` subcommand and it takes way
less time if you already have the repo in your local filesystem.
For instance, if I want to point you to line 11 of the `zshrc.local` file in my
`dotfiles` repo, I can run the following command:
```bash
$ gh browse zshrc.local:11
```
That would open a browser tab to
[https://github.com/jbranchaud/dotfiles/blob/main/zshrc.local?plain=1#L11](https://github.com/jbranchaud/dotfiles/blob/main/zshrc.local?plain=1#L11).
If I wanted a range of lines, I could change it from `11` to, say, `11-27`:
```bash
$ gh browse zshrc.local:11-27
```
And I would see this in the browser --
[https://github.com/jbranchaud/dotfiles/blob/main/zshrc.local?plain=1#L11-L27](https://github.com/jbranchaud/dotfiles/blob/main/zshrc.local?plain=1#L11-L27).
Both of these URLs are pointing to the `main` branch. If I instead want to
reference a specific commit, I can use the `--commit` flag.
```bash
$ gh browse zshrc.local:11-27 --commit=f2f9e78d4fc784643f725c88f7a5a7a077e7f261
```
I grabbed that from the latest commit in `git log`. That opens to
[https://github.com/jbranchaud/dotfiles/blob/f2f9e78d4fc784643f725c88f7a5a7a077e7f261/zshrc.local?plain=1#L11-L27](https://github.com/jbranchaud/dotfiles/blob/f2f9e78d4fc784643f725c88f7a5a7a077e7f261/zshrc.local?plain=1#L11-L27).
Another way of doing that would be to use `git rev-parse HEAD`:
```bash
$ gh browse zshrc.local:11-27 --commit=$(git rev-parse HEAD)
```
See `gh browse --help` for more details.
@@ -1,58 +0,0 @@
# Process JSON Output From gh With jq
The `gh` (GitHub) CLI is useful for accessing data about your profile and
projects from the terminal. With the `--json` flag, we can access the data in a
structured way which is useful for scripting.
Here is an example of pulling a list of all my repositories, limiting each
entity to just the `nameWithOwner` and `description`:
```bash
gh repo list --limit 1000 --json nameWithOwner,description
[
{
"description": "My personal site -- joshbranchaud.com",
"nameWithOwner": "jbranchaud/personal-site"
},
{
"description": "Private repo for the NOTES.md of my TIL repo",
"nameWithOwner": "jbranchaud/til-notes-private"
},
...
]
```
If I'm using the `--json` flag, then I can add in the `--jq` flag to apply a
`jq` query for additional processing of the output.
Here I convert it to a series of tuples:
```bash
gh repo list --limit 1000 --json nameWithOwner,description \
--jq '.[] | [.nameWithOwner, .description]'
[
"jbranchaud/personal-site",
"My personal site -- joshbranchaud.com"
]
[
"jbranchaud/til-notes-private",
"Private repo for the NOTES.md of my TIL repo"
]
...
```
Then I can add one more pipe to that `jq` query to turn it into _tab-separated
values_ using
[`@tsv`](https://jqlang.org/manual/v1.5/#format-strings-and-escaping):
```bash
gh repo list --limit 1000 --json nameWithOwner,description \
--jq '.[] | [.nameWithOwner, .description] | @tsv'
jbranchaud/personal-site My personal site -- joshbranchaud.com
jbranchaud/til-notes-private Private repo for the NOTES.md of my TIL repo
...
```
This is useful because I can then pipe it to another program, such as an `fzf`
command like [this repo selector that opens the selected one in the
browser](https://github.com/jbranchaud/dotfiles/commit/f964ca10c6c4db3475411c2991dc2f1dfd18c818).
@@ -1,20 +0,0 @@
# 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.
@@ -1,38 +0,0 @@
# 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.
@@ -1,32 +0,0 @@
# Check Ruby Version For Production App
While deploying a fresh Rails app to Heroku recently, I ran into an issue. The
`it` block argument wasn't working despite being on Ruby 4.0. Or so I thought.
Running the following command reported the Ruby version of that Heroku server
instance:
```bash
heroku run -- ruby --version
Running ruby --version on ⬢ my-app... up, run.3090
ruby 3.3.9 (2025-07-24 revision f5c772fc7c) [x86_64-linux]
```
I was on `3.3.9` which must have been the fallback default at the time.
Though I had set the Ruby version in my `.ruby-version` file, I had neglected to
specify it in the `Gemfile` as well. Once I added it to the `Gemfile` and
redeployed, my Heroku server instance was running the expected version of Ruby.
```bash
heroku run -- ruby --version
Running ruby --version on ⬢ my-app... up, run.5353
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [x86_64-linux]
```
Note: because [I have set `HEROKU_ORGANIZATION` and
`HEROKU_APP`](set-default-team-and-app-for-project.md) in my environment
(`.envrc`) for the local copy of the app, I don't need to specify those when
running the `heroku run` command above.
See `heroku run --help` for more details.
@@ -1,34 +0,0 @@
# 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.
@@ -1,39 +0,0 @@
# 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.
@@ -1,12 +0,0 @@
# Hide Overflowing Text For Google Sheets Column
I imported a big CSV into a new Google Sheets document. This included a
"Description" column with many of the descriptions varying between 50 and 80
characters. The bottom line is that the description column was flowing over the
top of the columns next to it. Instead of expanding the width of that column as
far as the largest description, I wanted to hide the _overflow_.
The way to do this in Google Sheets is to highlight the entire column by
clicking on the column grouping. Then under the _Format_ menu item is a
_Wrapping_ submenu. The _Clip_ option is what I was looking for because it clips
the text that gets shown at the edge of the column.
@@ -5,6 +5,8 @@ an array-like object with all of the arguments to the function. Even if not
all of the arguments are referenced in the function signature, they can
still be accessed via the `arguments` object.
> For ES6+ compatibility, the `spread` operator used via [rest parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters) is preferred over the `arugments` object when accessing an abritrary number of function arguments.
```javascript
function argTest(one) {
console.log(one);
@@ -1,42 +0,0 @@
# 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é'
```
@@ -1,34 +0,0 @@
# Get User's Preferred Language From Browser
A great way to determine a user's preferred language if you aren't able to ask
them directly is to look at the language setting for their browser's UI.
We can get this from the instance of
[`Navigator`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator) in the
user's JavaScript runtime within the browser.
My browser's UI is set to US English, which I can verify like so:
```javascript
> navigator.language
'en-US'
```
This is useful for all sorts of things like [formatting dates for
display](basic-date-formatting-without-a-library.md):
```javascript
> const now = new Date();
> Intl.DateTimeFormat(navigator.language).format(now)
'5/14/2026'
```
Or for [formatting other kinds of units for
display](formatting-values-with-units-for-display.md):
```javascript
> const milesFormat =
Intl.NumberFormat(navigator.language, { style: "unit", unit: "mile" });
> milesFormat.format(1500)
"1,500 mi"
```
@@ -1,28 +0,0 @@
# 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.
@@ -1,31 +0,0 @@
# 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)
-26
View File
@@ -1,26 +0,0 @@
# Count Number Of Tokens In A File
Over time you have accumulated a bunch of small directives, corrections, and
project details in your `CLAUDE.md` or `AGENTS.md` file. The file doesn't seem
too big, but you are mindful that it is being included in every prompt. How many
tokens is it eating from the context window?
OpenAI's BPE (Byte Pair Encoding) tokenization library,
[`tiktoken`](https://github.com/openai/tiktoken), is an open-source Python
package. If it is installed on our machine, then we can use it as part of the
following one-liner to check a file:
```bash
python -c "import tiktoken, sys; print(len(tiktoken.encoding_for_model('gpt-4o').encode(open(sys.argv[1], 'r', encoding='utf-8').read())))" \
AGENTS.md
1018
```
I ran this against the `AGENTS.md` file in a team project I'm on. It came out to
1018 tokens. This is a very good approximation based on the tokenizer trained
for `gpt-4o`. The tokenizers may vary a little from model to model, but the
differences for our purposes here are going to be negligible.
This one-liner gets the "first" argument to the command, reads it in, and runs
that string against the tokenizer. The length of the tokenized encoding is then
printed.
@@ -1,51 +0,0 @@
# 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.
@@ -1,27 +0,0 @@
# 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.
@@ -1,16 +0,0 @@
# Clean Up Item Layout In Finder Window
Sometimes while doing a bunch of manual drag-n-drop of files and folders in a
Finder.app window, I'll end up with a visual mess. Compared to other folders,
nothing is organized on the grid.
I can tell Finder.app to clean that up with the _Clean Up_ menu option.
While focused on the folder that I'm concerned about, I can go to _View_ >
_Clean Up_ in the top menu. Everything will snap into place.
On the specific Finder.app window, there is also a triple-dot actions menu that
appears on the top right. The _Clean Up_ action is available there as well.
There is also a _Clean Up By_ option which is a nice way to organize by some
attribute, such as the type (e.g Folder/File and extension).
@@ -1,21 +0,0 @@
# Control Which Monitor App Switcher Appears On
For the most part when I hit `cmd+tab` (and `cmd+shift+tab`) to switch between
apps, the visual switcher UI (which shows a row of the open apps) appears on my
main monitor. However, sometimes I will be hitting `cmd+tab` and nothing shows
up on my main monitor. I look to the right at my side monitor and there is the
app switcher UI.
Why is it appearing over there all of a sudden?
The reason is that the app switcher UI is anchored to the same screen where the
doc is located. Though the doc defaults to my main monitor, if I access the doc
from the side monitor, now it is anchored there.
To switch it back, I just have to make the doc slide up on my main monitor by
running my mouse down to the bottom of that screen.
The switch up was because I accidentally accessed the doc on my side monitor
without realizing.
[source](https://superuser.com/a/744680)
@@ -1,39 +0,0 @@
# 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.
@@ -1,41 +0,0 @@
# 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.
-36
View File
@@ -1,36 +0,0 @@
# 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
```
@@ -1,30 +0,0 @@
# 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.
@@ -1,89 +0,0 @@
# Read The Lid Angle Sensor For A MacBook
MacOS has a bunch of internal HID (Human Interface Device) data that can surface
details about all kinds of "devices" that comprise your machine. Some obvious
ones are the keyboard and trackpad as well as external mice and keyboards. The
battery and power source details are another which is sometimes integrated into
tools that display battery status (e.g.
[`tmux-battery`](https://github.com/tmux-plugins/tmux-battery)), though it uses
`pmset` directly). And many, many more.
One example I'd never considered is that there is a sensor for the lid angle of
the laptop that can tell the system whether the lid is open or closed and how
open it is (i.e. at what angle). There is no public interface for this lid angle
sensor, but people exploring all the HID devices have found the identifiers that
correspond to it (e.g.
[`pybooklid`](https://github.com/tcsenpai/pybooklid/blob/main/pybooklid/macbook_lid.py)).
Here is a minimal script that uses `uv`, `hidapi` (python bindings), and
`libhidapi` (shared runtime lib for those bindings):
```python
#!/usr/bin/env -S uv run --quiet --script
# /// script
# requires-python = ">=3.10"
# dependencies = ["hidapi"]
# ///
"""Print MacBook lid angle in degrees."""
import os, sys
if sys.platform == "darwin":
brew = "/opt/homebrew/lib"
if os.path.exists(brew):
os.environ["DYLD_LIBRARY_PATH"] = f"{brew}:{os.environ.get('DYLD_LIBRARY_PATH','')}"
import hid
VENDOR_ID, PRODUCT_ID = 0x05AC, 0x8104
USAGE_PAGE, USAGE = 0x0020, 0x008A
REPORT_ID = 1
def read_angle():
for info in hid.enumerate(VENDOR_ID, PRODUCT_ID):
if info.get("usage_page") == USAGE_PAGE and info.get("usage") == USAGE:
d = hid.device()
path = info["path"]
d.open_path(path if isinstance(path, bytes) else path.encode())
try:
data = d.get_feature_report(REPORT_ID, 8)
if data and len(data) >= 3:
return float((data[2] << 8) | data[1])
finally:
d.close()
return None
if __name__ == "__main__":
a = read_angle()
if a is None:
sys.exit("sensor not available")
print(f"{a:.0f}")
```
These IDs and usage values are the undocumented values that allow the script to
navigate specifically to the lid angle sensor and specifically to the usage page
and value that represent the current lid angle reading.
```
VENDOR_ID, PRODUCT_ID = 0x05AC, 0x8104
USAGE_PAGE, USAGE = 0x0020, 0x008A
REPORT_ID = 1
```
I added [this
script](https://github.com/jbranchaud/dotfiles/blob/cbc7196607d1d6b25885f5387ca85b658bd765de/bin/lidangle)
to [my dotfiles](https://github.com/jbranchaud/dotfiles) and made it executable
(`chmod +x bin/lidangle`) so that I can try it out. I first ran it while it was
closed and connected to my external monitor (`0`), then I opened it as far as it
could go (`129`), and then I tried angling it close to what I thought was 90
degress (`92`, so close).
```bash
lidangle
0
lidangle
129
lidangle
92
```
@@ -1,20 +0,0 @@
# Reveal Location Of File In Finder.app
In the terminal I have the path to an image file. I want to open Finder.app to
the location of that image file so that I can drag and drop it into a file
upload area in the browser.
Instead of opening a Finder.app window and navigating directory by directory to
the location, I can use the `open` command. Using `open` directly with the image
file will open the image in Preview.app. I want to reveal the directory that the
image file is in within Finder.app. _Reveal_ is the keyword and the `-R` flag
does just that.
Here is an example of this that I actually ran when uploading a screenshot that
went into [this blogmark post](https://still.visualmode.dev/blogmarks/255):
```bash
$ open -R /Users/lastword/images/tiobe-index-graph-march-2026.png
```
See `man open` for more details.
@@ -1,42 +0,0 @@
# Generate Permutations Of All Valid 9-ball Racks
I wanted to produce a full listing of all valid rack arrangements for the game
of [9-ball](https://en.wikipedia.org/wiki/Nine-ball). The constraints on how a
9-ball rack can be arranged are, first, that the 1 ball must be placed at the
head of the diamond and, second, that the 9 ball must be placed at the center of
the diamond. After that, all other balls (2 through 8) can be placed in any
arrangement.
Because each of those seven remaining balls can be arranged in distinct
orderings where each ball is placed once, this is a
[_permutation_](https://en.wikipedia.org/wiki/Permutation) problem.
> In elementary combinatorics, the k-permutations, or partial permutations, are
> the ordered arrangements of k distinct elements selected from a set. When k is
> equal to the size of the set, these are the permutations in the previous
> sense.
For this problem, the seven distinct elements can be arranged into `7!` (seven
factorial) unique permutations. That is, 5040 permutations.
I can use [Ruby's `Array#permutations`
method](https://docs.ruby-lang.org/en/4.0/Array.html#method-i-permutation) to
enumerate these 5040 permutations like so:
```ruby
[2,3,4,5,6,7,8].permutation.map do |perm|
[1, *perm[0..2], 9, *perm[3..7]]
end.to_a
=> [[1, 2, 3, 4, 9, 5, 6, 7, 8],
[1, 2, 3, 4, 9, 5, 6, 8, 7],
[1, 2, 3, 4, 9, 5, 7, 6, 8],
[1, 2, 3, 4, 9, 5, 7, 8, 6],
[1, 2, 3, 4, 9, 5, 8, 6, 7],
[1, 2, 3, 4, 9, 5, 8, 7, 6],
[1, 2, 3, 4, 9, 6, 5, 7, 8],
...
[1, 8, 7, 6, 9, 5, 3, 2, 4],
[1, 8, 7, 6, 9, 5, 3, 4, 2],
[1, 8, 7, 6, 9, 5, 4, 2, 3],
[1, 8, 7, 6, 9, 5, 4, 3, 2]]
```
@@ -1,7 +1,8 @@
# Create Umbrella Task For All Test Tasks
When I was first sketching out the [`mise` tasks](https://mise.jdx.dev/tasks/running-tasks.html) for a Rails app, I added
the following two tasks. One is for running all the `rspec` tests. The other is
When I was first sketching out the [`mise`
tasks](https://mise.jdx.dev/tasks/running-tasks.html) for a Rails app, I added
the following two tasks. One is for running all the `rspec` tests. The Other is
for running all the `vitest` (JavaScript) tests.
```toml
@@ -48,4 +49,5 @@ Running `mise run test:all` won't execute its own command, but because it
depends on all other `test:*` tasks, the tests will get run through those
dependencies.
This task naming pattern also allows for calling all tests with `mise run "test:**"`.
This task naming pattern also allows for calling all tests with `mise run
"test:**"`.
-27
View File
@@ -1,27 +0,0 @@
# Look In Ruby Version Dotfile
Newer versions of [`mise`](https://mise.jdx.dev/dev-tools/) specifically only
look for tool versions in `mise.toml` as well as the asdf `.tool-versions` file.
A lot of Ruby projects use the `.ruby-version` file to indicate the Ruby version
of a project. To continue to use the `.ruby-version` file instead of migrating
to `mise.toml`, you need to tell `mise` that you prefer to use the idiomatic
version file.
I added the following line to my
[`~/.config/mise/config.toml`](https://github.com/jbranchaud/dotfiles/commit/8edeb7a9c53500e89e88b4079cbd1859ebebcbda)
file:
```toml
idiomatic_version_file_enable_tools = ["ruby"]
```
Now, whenever `mise` is looking for the specified Ruby version of a project, it
will also look for `.ruby-version`.
Here is a [full list of idomatic version files supported by
`mise`](https://mise.jdx.dev/configuration.html#idiomatic-version-files).
See
[`idiomatic_version_file_enable_tools`](https://mise.jdx.dev/configuration/settings.html#idiomatic_version_file_enable_tools)
as well as the [Ruby-specific documentation](https://mise.jdx.dev/lang/ruby.html#ruby-version-and-gemfile-support)
for more details.
-37
View File
@@ -1,37 +0,0 @@
# Override Your Project Mise File
A project I'm working on has a version-controlled `.mise.toml` file in it. Some
changes were made to that recently that introduce some env vars that conflict
with my setup. If I make edits to that file, then I have a modified version of
`.mise.toml` sitting in my Git working copy.
```
# .mise.toml
[env]
CONFIG_SETTING = "project"
```
Instead, I can rely on the loading precedence rules of `mise` to override those
project settings with my individual settings. I can do that with the
`.mise.local.toml` file which is played on top of any `mise` configuration from
files further down the precedence chain.
```
# .mise.local.toml
[env]
CONFIG_SETTING = "override"
```
Assuming I have `mise` setup with my shell environment to automatically load in
these files, I can now check what takes precedence:
```bash
$ echo $CONFIG_SETTING
override
```
Make sure `.mise.local.toml` is included in the `.gitignore` file to avoid
checking in your personal environment overrides.
To be sure about what files are loaded and in what order, give `mise cfg` a try.
I discuss that in more detail in [List The Files Being Loaded By Mise](list-the-files-being-loaded-by-mise.md).
@@ -1,38 +0,0 @@
# Pick From Tasks Using Interactive Picker
In [Add Mise Tasks For Common Workflow
Commands](https://www.visualmode.dev/add-mise-tasks-for-common-workflow-commands),
I wrote about a set of tasks I added as shortcuts for connecting to the `rails console` in various environments.
```toml
# mise.toml
[tasks."console:staging"]
description = "Open a Rails console on staging"
run = "ssh -t my-app-staging dokku run my-app rails console"
[tasks."console:prod"]
description = "Open a Rails console on production"
run = "ssh -t my-app-prod dokku run my-app rails console"
```
When a project is configured with multiple `mise` tasks like this, we can invoke
`mise run` without any specific arguments and it will prompt you with an
interactive picker. The picker will populate with all the tasks like so:
```bash
mise run
Tasks
Select a task to run
console:prod Open a Rails console on production
console:staging Open a Rails console on staging
/
esc clear filter • enter confirm
```
We can navigate between the options with the arrow keys (and if we exit _filter_
mode by hitting `esc`, then `j/k` also work to move down and up). While in
_filter_ mode, we can type into the prompt which will filter the list of
commands down to just the partial matches.
Once we're targeting the task we want to run, we hit `enter` and the task is
executed.
@@ -1,29 +0,0 @@
# Search Through Bin Paths For Tool Locations
The `mise bin-paths` command will list all the bin paths that are managed by
`mise`. When you tell `mise` to install a tool, it installs a specific version
at a location where its binaries can be made accessible on the system path.
While `mise ls` is useful for seeing what is installed by `mise` and at what
version, the `bin-paths` command can tell you where those tool installations
with their binaries are located.
Combine this with `grep` or `rg` to narrow down the results to tools by a
specific name:
```bash
mise bin-paths | rg 'neovim'
/Users/lastword/.local/share/mise/installs/npm-neovim/5.4.0/bin
/Users/lastword/.local/share/mise/installs/pipx-neovim-remote/2.5.1/bin
/Users/lastword/.local/share/mise/installs/neovim/0.11.6/bin
```
I can then look in one of these directories to see the one or more binaries that
they include. For instance, here is what is in the `node` bin path:
```bash
ls /Users/lastword/.local/share/mise/installs/node/22.22.0/bin
 ./  ../  claude@  corepack@  node*  npm*  npx@
```
See `mise bin-paths --help` for more details.
@@ -1,40 +0,0 @@
# 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;
```
@@ -1,46 +0,0 @@
# 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).
-23
View File
@@ -1,23 +0,0 @@
# 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.
Submodule notes deleted from 897184eb02
@@ -1,23 +0,0 @@
# 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.
@@ -1,29 +0,0 @@
# Avoid Vulnerabilities In New Package Versions
It seems like every week there is a new supply chain attack where malicious code
is embedded in a popular, widely-used OSS package. This week's is
[axios](https://www.stepsecurity.io/blog/axios-compromised-on-npm-malicious-versions-drop-remote-access-trojan).
The [`pnpm` package manager](https://pnpm.io/) has a nice feature that helps
avoid installing these vulnerable package versions in the first place.
> To reduce the risk of installing compromised packages, you can delay the
> installation of newly published versions. In most cases, malicious releases
> are discovered and removed from the registry within an hour.
The [`minimumReleaseAge` config option](https://pnpm.io/settings#minimumreleaseage) tells `pnpm` to not install
a dependency (including transitive ones) until it has been released for at least
that many minutes.
For instance, if you wanted to set this to 72 hours, then you'd set this option
to `4320` minutes like so:
```
$ pnpm config set minimum-release-age 4320 -g
```
The global flag (`-g`) will set that in your global config location, e.g.
`$XDG_CONFIG_HOME/pnpm/rc`. You could also add it specifically to your project
in the `pnpm-workspace.yaml` file.
[source](https://bsky.app/profile/styfle.dev/post/3miekuyeyrs2w)
@@ -1,39 +0,0 @@
# 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)
@@ -1,44 +0,0 @@
# Compute Median Instead Of Average
One of the first aggregate functions we might use in PostgreSQL, besides `sum`,
is `avg`.
```sql
select avg(book_count) as average_books_read
from (
select users.id, count(books.id) as book_count
from users
left join books
on books.user_id = users.id
where books.read_in_year = 2025
group by users.id
) as user_book_counts;
```
This computes the average of the set of values which sums them all up
and divides by the count. The average (maybe you've heard this also called the
_mean_) is not always the best way to understand data, especially when there are
outliers.
Instead, we might want to compute the _median_ value of our set of data. There
is no easily identifiable `median` aggregate function. Instead, we can use
`percentile_cont` with a value of `0.5`. This gets us the 50th percentile of our
set of data which is the definition of the _median_.
```sql
select percentile_cont(0.5) within group (
order by book_count
) as median_books_read
from (
select users.id, count(books.id) as book_count
from users
left join books on books.user_id = users.id and books.read_in_year = 2025
group by users.id
) as user_book_counts;
```
The full syntax for `percentile_cont` is `percentile_cong(precision) within
group (order by ...)` because this is an aggregiate that has to work with an
ordered-set of data.
[source](https://www.postgresql.org/docs/current/functions-aggregate.html)
@@ -1,58 +0,0 @@
# 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)
```
@@ -1,37 +0,0 @@
# 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;
```
@@ -1,35 +0,0 @@
# 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.
@@ -1,34 +0,0 @@
# Access Most Recent Return Value In REPL
One of my favorite features of Ruby's `irb` and `pry` are that you can use `_`
to reference the most recent return value. Often as we use an interpreter or
REPL, we end up with _intermediate_ values. That is, we've execute some kind of
statement which returned a value and we now want to use that resulting value in
our next statement. Python also supports `_`.
Let's say I've run a statement that took a while to process, but I forgot to
assign it to a variable. Instead of re-running the whole thing, I can create a
variable that references the previous return value using `_`.
```python
>>> BytePairEncoding.train_bpe(long_text)
{'merge_rules': [...], 'vocab': {...}}
>>> result = _
>>> list(result.keys())
['merge_rules', 'vocab']
```
Even if I don't necessarily want to assign it a variable, it can be nice to
reference the previous value as I continue with what I'm doing:
```python
>>> result['merge_rules'][0][1]
256
>>> result['vocab'][_]
b'e '
```
Notice how the value from the first statement gets used as part of a `dict`
access.
[source](https://docs.python.org/3/tutorial/introduction.html#numbers)
@@ -1,44 +0,0 @@
# Access Variables Outside Loop Scope
Here is a function that loops over a list to find the first occurrence of a
falsy value.
```python
def find_false(self):
for item in self.items:
item_type = type(item)
print(f"Current item: {item} ({item_type})")
if not item:
break
print(f"First false item: {item} ({item_type})")
```
Notice how at the end of the function, outside of the loop, I am able to access
both `item` (defined in the loop definition) and `item_type` (defined within the
loop's body).
Both of these variables are defined, by the loop, in _function scope_ and are
accessible anywhere in the function after they have been defined.
The title of this TIL is a bit of a misnomer because Python doesn't have the
concept of a _loop scope_. There are two levels of scope in Python --
module/global scope and function scope.
I spend most of my time writing Ruby which also has _block scope_, so Python's
simplified two-level scoping took me by surprise.
Though the code sample above is contrived, this function scope assignment can be
taken advantage of with loop definitions in scenarios where you want to know
what the last `item` defined was before the loop terminated.
```python
for submission in submissions:
if passes(submission, criteria):
break
else:
raise ValueError("No submissions that meet given criteria")
print(f"Submit first passing submission: {submission.id}")
submit(submission)
```
@@ -1,76 +0,0 @@
# Argument Defaults Are Evaluated When Function Is Defined
When you define a function with any arguments that have default values, those
default values are evaluated and stored at the time that the function is defined
(i.e. when it is evaluated by the interpreter). This might feel counter
intuitive if you are coming from another language, like Ruby, where these kinds
of defaults are evaluated at call time. This is unremarkable for scalar values
like `4` or `"fallback"`. It's much more interesting when your defaults are
function calls.
What if our default is something like `datetime.now()`?
Here I've defined a `Timer` class that has a `start` and `stop` method. The
`stop` method can be called with a specific `datetime` value otherwise it falls
back to `datetime.now()` -- but when is _now_?
```python
from datetime import datetime, timezone
import time
class Timer:
def __init__(self):
self._start = None
self._stop = None
def start(self):
self._start = datetime.now(timezone.utc)
self._stop = None
def stop(self, at=datetime.now(timezone.utc)):
print(f"now: {datetime.now(timezone.utc)}")
print(f" at: {at}")
self._stop = at
elapsed = self._stop - self._start
return elapsed
```
Here I instantiate a timer, call `start`, sleep for 5 seconds, and then call
`stop`.
```python
timer = Timer()
timer.start()
time.sleep(5)
print(f"Elapsed: {timer.stop()}")
```
Here is what gets printed to `stdout`:
```
now: 2026-05-22 00:45:05.654878+00:00
at: 2026-05-22 00:45:00.649699+00:00
Elapsed: -1 day, 23:59:59.999875
```
Notice that the actual _now_ (when the `stop` method is running) is about 5
seconds after the value of `at`. That is because `at`, which takes on the
default argument value, is `datetime.now()` as evaluated at the time the
function is interpreted. It is for that same reason that `self._stop` ends up
being just a hair earlier than the call to `start` which sets `self._start`.
Which explains why the _elapsed_ time is a negative value.
To avoid this awkwardness all together, set the default as `None` and then
override `None` at the start of the function:
```python
def stop(self, at = None):
if at == None:
at = datetime.now(timezone.utc)
# ...
```
@@ -1,38 +0,0 @@
# Assert Is Only A Development Check
The `assert` keyword is used in Python to write a statement that will check some
assertion and raise an error if it isn't met. This is only meant to be used as a
check during development because it can be easily optimized out of the code.
```python
stuff = None
assert stuff, "We need to have some stuff to proceed"
print(f"We have {stuff or 'something'}!")
```
If I execute this code with `python`, it will raise on that second line of code.
```bash
python assert_example.py
Traceback (most recent call last):
File "/Users/lastword/dev/jbranchaud/py-vmt/assert_example.py", line 3, in <module>
assert stuff, "We need to have some stuff to proceed"
^^^^^
AssertionError: We need to have some stuff to proceed
```
This `assert` statement will be stripped out of the compiled bytecode if the
`-O` (capital o) flag is used. Notice how running the same file with that flag
does not lead to an `AssertionError`.
```python
python -O assert_example.py
We have something!
```
If I want to make sanity checks for situations that would be caused by a bug in
the code, an `assert` statement can be a good candidate. However, if I am making
runtime checks like validating user input, then an `if` statement and raising
something like a `ValueError` is better.
@@ -1,47 +0,0 @@
# Avoid Modification With Frozen Dataclass
The `@dataclass` decorator can be set as _frozen_ to prevent modification of
values on instances of that `dataclass`.
Without making it frozen, I can easily subvert validations by changing the value
of attributes after the `__post_init__` validations are called.
```python
>>> config = BPEConfig(300, []) # passes validations
>>> config.vocab_size = 22 # this is invalid, wish this was prevented
```
Here is the updated `@dataclass` declaration with `frozen=True` passed as a
parameter.
```python
from dataclasses import dataclass
from typing import ClassVar
@dataclass(frozen=True)
class BPEConfig:
BASE_VOCAB_SIZE: ClassVar[int] = 256
vocab_size: int
special_tokens: list[str]
def __post_init__(self):
if self.vocab_size < self.BASE_VOCAB_SIZE:
msg = f"vocab_size ({self.vocab_size}) must be greater than or equal to BASE_VOCAB_SIZE ({self.BASE_VOCAB_SIZE})"
raise ValueError(msg)
```
Now I am prevented from modifying a scalar value like `vocab_size` after the
instance has been created.
```python
>>> config = BPEConfig(300, [])
>>> config.vocab_size = 22
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<string>", line 4, in __setattr__
dataclasses.FrozenInstanceError: cannot assign to field 'vocab_size'
```
This doesn't prevent you from modifying the contents of attributes that are
`list` or `dict` types.
@@ -1,50 +0,0 @@
# Check If Package Is Installed With Pip
I recently installed PyTorch, but when I tried using it, I was getting an error
about `numpy` not being installed. I was kind of surprised by that because I
thought I would have already had that.
I wanted to check, so I asked with `pip show`:
```bash
python3 -m pip show numpy
WARNING: Package(s) not found: numpy
```
I can even list everything that is installed with `pip` using `pip list` like
so:
```bash
python3 -m pip list
Package Version Build
------------------ --------- -----
certifi 2026.1.4
cffi 2.0.0
charset-normalizer 3.4.4
click 8.3.1
commonmark 0.9.1
cryptography 46.0.3
docutils 0.22.4
filelock 3.24.2
fsspec 2026.2.0
idna 3.11
Jinja2 3.1.6
...
```
I then installed `numpy` (`python3 -m pip install numpy`) and how I can use `pip
show` again to confirm that.
```bash
python3 -m pip show numpy
Name: numpy
Version: 2.4.2
Summary: Fundamental package for array computing in Python
Home-page: https://numpy.org
Author: Travis E. Oliphant et al.
Author-email:
License-Expression: BSD-3-Clause AND 0BSD AND MIT AND Zlib AND CC0-1.0
Location: /Users/lastword/.local/share/mise/installs/python/3.12.12/lib/python3.12/site-packages
Requires:
Required-by:
```
@@ -1,53 +0,0 @@
# Check Precondition Before Click Arg Parsing
When setting up various [Click](https://click.palletsprojects.com/en/stable/)
subcommands with options, I ran into an issue with the order of some validation
checks. I was putting the same precondition validation logic at the beginning of
several subcommands. I was also putting callback validations on specific options
to those subcommands. Ideally the option validations could rely on those
precondition validations. However, the option callbacks run before anything in
the body of the subcommands.
The solution was to move those preconditions out of the subcommand body
(simplifying the subcommand) and into a `click.Command` subclass.
To demonstrate that, I'll first show the `click.Command` subclass:
```python
class RequireActiveSessionCommand(click.Command):
def parse_args(self, ctx, args):
if ctx.obj.active_session is None:
msg = "No active session being tracked. Start a session first."
raise click.UsageError(msg)
return super().parse_args(ctx, args)
```
The only thing this subclass overrides is `parse_args` where it gets ahead of
the standard arg parsing logic to first check the precondition. In this case, I
check that there is an active session. If there isn't, then I can raise a
`click.UsageError`. Otherwise, it delegates back to the super-class
implementation of `parse_args`.
This subclass then gets used for the commands that need to enforce this
precondition. Two prime examples of that are the `stop` and `cancel` subcommands.
```python
@cli.command(cls=RequireActiveSessionCommand)
@click.option("--at", help='Hours previous to end the timer, e.g. "2 hours ago"', callback=validate_stop_at)
@pass_cli
def stop(cli_ctx: CliContext, at: datetime) -> None:
# ... implementation omitted
@cli.command(cls=RequireActiveSessionCommand)
@pass_cli
def cancel(cli_ctx: CliContext):
# ... implementation omitted
```
Other subcommands, like `start` and `status` that don't need to enforce this
precondition use the `@cli.command()` decorator without passing in a custom
subclass.
This example is pulled directly from [this commit](https://github.com/jbranchaud/py-vmt/commit/505109b7a4013e05f085cded666c6b1ac7c3c250)
of my [`py-vmt` time tracker tool](https://github.com/jbranchaud/py-vmt).
@@ -1,40 +0,0 @@
# Control Passing Of Time In Tests
While it is nice to be able to write pure functional code, our software still
lives in the real world and may have to relate to or depend on the passing of
time. In order to test this kind of code, we need time to behave in a reliable,
deterministic way. One of the best ways to create a testing environment where
that is true is to bring in tooling that hijacks time.
The [`freezegun` module](https://github.com/spulec/freezegun) is a great tool
for that job. We can use it to freeze time at a specific testable point, advance
time a specific amount, and much more.
Here is an example from the tests for [my CLI-based time tracking
app](https://github.com/jbranchaud/py-vmt/blob/acb26e4840279d936a12f16c505ca7e75e9a6d20/tests/src/py_vmt/test_cli.py#L21)
where I freeze time before starting a session. That gives me a chance to assert
about the exact start time that is output by the command. Then I can advance
time a little and assert that the `status` command outputs the correct thing.
```python
import datetime
from freezegun import freeze_time
# some other test setup omitted ...
initial_datetime = datetime.datetime(
2026, 3, 14, 15, 5, 11, 0, datetime.timezone.utc
)
with freeze_time(initial_datetime) as frozen_datetime:
# start a session
start_result = runner.invoke(cli, ["start", "my-project"])
output = "Started tracking 'my-project' at 10:05AM"
assert output in start_result.output
frozen_datetime.tick(delta=datetime.timedelta(minutes=30))
# check status
status_result = runner.invoke(cli, ["status"])
output = "Tracking 'my-project' for 30m (since 10:05AM)"
assert output in status_result.output
```
@@ -1,44 +0,0 @@
# Create A Range Of Descending Values
A typical use of `range` looks something like this:
```python
>>> list(range(1, 5))
[1, 2, 3, 4]
```
Which is equivalent to this one where we give a `step` value of `1`.
```python
>>> list(range(1, 5, 1))
[1, 2, 3, 4]
```
If we try to create a _negative range_, that is, a range of values in decreasing
order, we get an empty list.
```python
>>> list(range(0, -7))
[]
```
That's because the `step` value still defaults to `1`. And there are no positive
steps between `0` and `-7`. So, let's give `range` a `step` value of `-1`.
```python
>>> list(range(0,-7, -1))
[0, -1, -2, -3, -4, -5, -6]
```
One practical use case of a negative range like this is using a list
comprehension to transform it into a list of the _last seven days_.
```python
>>> from datetime import datetime, timedelta
>>> [datetime.now().date() + timedelta(days=days) for days in range(0,-7, -1)]
[datetime.date(2026, 3, 19), datetime.date(2026, 3, 18), datetime.date(2026, 3, 17), datetime.date(2026, 3, 16), datetime.date(2026, 3, 15), datetime.date(2026, 3, 14), datetime.date(2026, 3, 13)]
```
Of course this could have been written with a positive range and then
subtracting the `timedelta`. I like that I have the option of doing this in
whatever way makes the code most readable.
-46
View File
@@ -1,46 +0,0 @@
# Deduplicate A List Into A Tuple
A `list` is not hashable which means you can't use it for things like `dict`
keys or cache keys. Instead you need to convert it into something like a `set`
or a `tuple`.
Here is an example list:
```python
>>> l1 = [3,4,1,2,5,4,1]
```
Turning this list into a `set` or `frozenset` is straightforward:
```python
>>> set(l1)
{1, 2, 3, 4, 5}
>>> frozenset(l1)
frozenset({1, 2, 3, 4, 5})
```
If you're trying to preserve the order after deduplicating, then you'll want to
use a `tuple` instead of a `set`. In order to deduplicate while maintaining the
ordering, you can exploit the fact that `dict` keys maintain their order. A
`list` can be transformed into the keys of a `dict` with
[`dict.fromkeys`](https://docs.python.org/3/library/stdtypes.html#dict.fromkeys):
```python
>>> dict.fromkeys(l1)
{3: None, 4: None, 1: None, 2: None, 5: None}
```
And here is your `tuple` which extracts the keys of the `dict`:
```python
>>> tuple(dict.fromkeys(l1))
(3, 4, 1, 2, 5)
```
By comparison, here is the `tuple` transformed directly from the `list` without
deduplication.
```python
>>> tuple(l1)
(3, 4, 1, 2, 5, 4, 1)
```
@@ -1,60 +0,0 @@
# Define Sequence Of Tests With Parametrize Decorator
I have a function that I want to test across a bunch of different inputs. That
way I can make sure the logic of that function handles all the different
scenarios I have in mind.
While working on [`py-vmt`](https://github.com/jbranchaud/py-vmt), I started by
writing a big single test function with a sequence of variable assignments and
`assert` statements. Here's my starting point:
```python
def test_format_time_delta_everything():
# less than a minute
thirty_seconds = timedelta(seconds=30)
assert "30s" == format_time_delta(thirty_seconds)
# one minute exactly
one_minute = timedelta(seconds=60)
assert "1m" == format_time_delta(one_minute)
# more than a minute
assert "1m30s" == format_time_delta(one_minute + thirty_seconds)
# bunch of minutes and seconds
delta = timedelta(minutes=24, seconds=8)
assert "24m8s" == format_time_delta(delta)
# one hour exactly
one_hour = timedelta(hours=1)
assert "1h" == format_time_delta(one_hour)
# more than one hour
assert "1h24m" == format_time_delta(one_hour + delta)
```
I knew I would eventually need to break it up into individual test functions,
but I couldn't bare to start there because it seemed quite repetitive.
There is another way to approach this without all the duplication. Pytest comes
with [a "parametrize" decorator](https://docs.pytest.org/en/stable/example/parametrize.html). This is
used to define a set of test data (and expected values) that will get passed
one-by-one to the test function as parameters.
```python
@pytest.mark.parametrize("input,expected", [
(timedelta(seconds=30), "30s"),
(timedelta(seconds=60), "1m"),
(timedelta(seconds=90), "1m30s"),
(timedelta(minutes=24, seconds=8), "24m8s"),
(timedelta(hours=1), "1h"),
(timedelta(hours=1, minutes=24, seconds=8), "1h24m"),
])
def test_format_time_delta(input, expected):
assert format_time_delta(input) == expected
```
I ditch all of the duplication this way. I define a list of tuples that
represent my input values and expected values. Then the body of the test can be
minimal. And I get a separate test execution for each parameter tuple making it
easier to see fine-grained pass/fail results.
@@ -1,42 +0,0 @@
# Define Typed Class Interface With Protocol
In [`py-vmt`](https://github.com/jbranchaud/py-vmt) I am defining different
storage access layers for the CLI to use. I want a consistent interface that the
core CLI logic can depend on regardless of whether it is a JSON file or a SQLite
database. To achieve that I can define a class of unimplemented functions that
inherits from
[`typing.Protocol`](https://typing.python.org/en/latest/spec/protocol.html).
```python
from typing import Protocol
class SessionRepository(Protocol):
def active_session(self) -> Session | None: ...
def write_active_session(self, session) -> None: ...
def append_session(self, session) -> None: ...
def all_sessions(self) -> list[Session]: ...
def clear_active_session(self) -> None: ...
```
Notice that none of these have default implementations. The `...` indicates that
class implementing this protocol will define the implementation of those
functions.
Now, my `CliContext` class, which needs some kind of `SessionRepository` to
function can indicate as much in `__init__`.
```python
class CliContext:
def __init__(self, verbose: bool, repo: SessionRepository | None = None) -> None:
self.verbose: bool = verbose
self.active_session: Session | None = None
self.repo: SessionRepository = repo or JsonRepository()
self.active_session = self.repo.active_session()
```
If `JsonRepository` doesn't define all of the methods specified in the protocol,
then a type error will occur wherever it clashes with `SessionRepository`. Now
as I implement `SqliteRepository` I have a standard interface to build against
that I know I can seamlessly swap in.
[source](https://typing.python.org/en/latest/reference/protocols.html#simple-user-defined-protocols)
@@ -1,53 +0,0 @@
# Easy Key-Value Aggregates With defaultdict
The `collections` module has the `defaultdict` object that can be used to
aggregate values tied to a key. What sets this apart from simply using a `dict`
is that we get the base value for free. So if our aggregate value is a list,
then we get `[]` by default for each new key. In the same way, we'd get `0` if
it was constructed with `int`.
Here is the counter example from [Keep A Tally With
collections.Counter](keep-a-tally-with-collections-counter.md)
```python
from collections import defaultdict
def get_pair_counts(token_ids: list[int]) -> Counter:
"""Count how often each adjacent pair appears"""
counts = defaultdict(int)
for i in range(len(token_ids) - 1):
pair = (token_ids[i], token_ids[i + 1])
counts[pair] += 1
return counts
```
We never have to initially set a key to `0`. If the key is not yet present, then
`int()` (the zero-value constructor) is used as the `__missing__` value.
We can do the same with `list`:
```python
>>> import collections
>>> stuff = collections.defaultdict(list)
>>> stuff['alpha'].append(1)
>>> stuff['alpha']
[1]
>>> stuff['beta']
[]
```
In the same way, this uses `list()` as the `__missing__` value to start of each
key with an `[]`.
I find this so handy because in other languages I've typically had to do
something more like this:
```python
words_by_length = {}
for item in items:
if len(item) not in words_by_length:
words_by_length[len(item)] = []
words_by_length[len(item)].append(item)
```
This is much clunkier.
@@ -1,24 +0,0 @@
# Enable Pyright Type Checking In Cursor
In most ways [Cursor](https://cursor.com/), a clone of VS Code, behaves like VS
Code and uses the same extensions as VS Code. It even offers to clone all your
existing extensions and setup from VS Code when you first install it.
However, the Pyright type checking setup that I had in VS Code stopped working
when I opened up the same Python project in Cursor. It seems that to get Pyright
to reliably work with forks of VS Code, you need to use a compatible fork like
[Based Pyright](https://docs.basedpyright.com/latest/).
Once I installed _Based Pyright_ from the extension marketplace, I was able to
enable it in `.vscode/settings.json`:
```json
{
...,
"basedpyright.analysis.typeCheckingMode": "basic"
}
```
I may have needed to restart Cursor at this point, I cannot remember exactly.
However, once this setup was in place the helpful type checking errors started
appearing as red squiggles.
@@ -1,64 +0,0 @@
# Get Absolute Seconds From `timedelta` Object
The [`timedelta` object provided by
`datetime`](https://docs.python.org/3/library/datetime.html#timedelta-objects)
is a useful built-in concept for representing a duration of time.
```python
>>> from datetime import timedelta
>>> diff = timedelta(hours=1, minutes=1, seconds=6)
>>> diff.seconds
3666
```
It is pretty minimal though. There are only a couple things you can inspect
about it -- `days`, `seconds` (as I did in the snippet above), and
`microseconds`.
And perhaps that is enough to hint at the issue I recently ran into with it --
specifically that you can access both `days` and `seconds`.
Let's look at what happens when I have a `timedelta` with more than a day worth
of seconds.
```python
>>> diff = timedelta(seconds=(3600 * 24 + 1))
>>> diff.seconds
1
>>> diff.days
1
```
I thought `seconds` was going to produce `86401` instead of `1`. The reason is
because any amount of duration over a day gets converted into the `days` value
and its the remaining time smaller than a day that is represented by `seconds`.
In my [original implementation of
`format_time_delta`](https://github.com/jbranchaud/py-vmt/blob/c14eaa56cf5f5c6d0120a95f04f95a6c87443e1c/src/py_vmt/time_helpers.py#L11-L14),
I was trying to build a relative time string by converting `seconds` into hours,
minutes, and seconds. That approach falls apart as soon as the delta is greater
than a day.
```python
def format_time_delta(diff) -> str:
hours, remainder = divmod(diff.seconds, 3600)
minutes, remainder = divmod(remainder, 60)
seconds = remainder
# ...
```
Instead, I needed to reach for [the `total_seconds()` function](https://docs.python.org/3/library/datetime.html#datetime.timedelta.total_seconds).
This gives "the total number of seconds contained in the duration" and is
described as equivalent to `diff / timedelta(seconds=1)`.
Here is the [updated version of `format_time_delta`](https://github.com/jbranchaud/py-vmt/blob/ec1875a9d73552f5481e3945ddf522e94d0cc018/src/py_vmt/time_helpers.py?plain=1#L11-L16):
```python
def format_time_delta(diff: timedelta) -> str:
total_seconds = int(diff.total_seconds())
hours, remainder = divmod(total_seconds, 3600)
minutes, remainder = divmod(remainder, 60)
seconds = remainder
```
@@ -1,42 +0,0 @@
# Get Quotient And Remainder In One Operation
While writing some custom code to transform a number of seconds into the
constituent hours, minutes, and seconds, I found myself needing to get both the
quotient and remainder from a division between two numbers.
```python
>>> import math
>>> math.floor(3666 / 3600)
1
>>> 3666 % 3600
66
```
Instead, I can use Python's built-in
[`divmod`](https://docs.python.org/3/library/functions.html#divmod) function to
compute both values in one statement.
```python
>>> divmod(3666, 3600)
(1, 66)
```
The result is a tuple with the first value being my quotient (in this case, the
number of hours) and the remainder (the remaining number of seconds).
This kind of operation is known as [Euclidian
Division](https://en.wikipedia.org/wiki/Euclidean_division).
Here is a snippet of some actual code where I use this in
[`py-vmt`](https://github.com/jbranchaud/py-vmt/blob/b9eae8b258e9fd720cfa3bb63b601225df352051/src/py_vmt/time_helpers.py#L14-L16):
```python
def format_time_delta(diff: timedelta) -> str:
total_seconds = int(diff.total_seconds())
hours, remainder = divmod(total_seconds, 3600)
minutes, remainder = divmod(remainder, 60)
seconds = remainder
# ...
```
@@ -1,17 +0,0 @@
# Install With PIP For Specific Interpreter
The `pip` module can be invoked for any of its commands, such as install, using
a specific Python interpreter like so:
```bash
$ python3 -m pip install black
```
This avoid ambiguity between the version of Python I am using and version of the
package manager I'm using.
Similarly if I need to upgrade `pip`, I can do the following:
```bash
$ python3 -m pip install --upgrade pip
```
@@ -1,27 +0,0 @@
# Iterate First N Items From Enumerable
As I'm working through the 2nd chapter of [Build a Large Language Model (from
scratch)](https://still.visualmode.dev/blogmarks/227), I came across a code
example processing a dictionary of words. This example used a for loop to print
out each dictionary entry until an index of 50 was reached on then it did a
`break`.
This struck me as an odd way to grab and process N items from a list. I did some
searching and found `itertools` which provides
[`islice`](https://docs.python.org/3/library/itertools.html#itertools.islice).
```python
from itertools import islice
# preprocess words from a file into a word list
all_words = ... # not shown here
vocab = {token: integer for integer, token in enumerate(all_words)}
for item in islice(enumerate(vocab.items()), 50):
print(item)
```
The `islice` function is a better approach because the intention (to grab the
first 50 things) is encoded in the function call rather than buried in a loop
body. It also has equivalent memory efficiency to the original example because
it lazily processes the list of `vocab` items.
-34
View File
@@ -1,34 +0,0 @@
# Iterate Over A Dictionary
Let's say we have a `dict` that contains counts of occurrences for each word in
some sample text:
```python
words_frequency = {
"the": 4,
"a": 3,
"dog": 1,
"bone": 1,
"wants": 1,
...
}
```
Here is how we can iterate over the `dict`, accessing both the keys and values:
```python
for word, count in word_frequency.items():
print(f"- {word} appears {count} time{'' if count == 1 else 's'}")
```
Using the
[`items()`](https://docs.python.org/3/library/stdtypes.html#dict.items) method,
we're able to access both _key_ and _value_ with the for loop as it iterates.
Another approach is to loop directly on the `dict` which implicitly surfaces the
_key_ for iteration. This can then be used to get the value from the `dict`:
```python
for word in word_frequency:
print(f"- {word}: {word_frequency[word]}
```
@@ -1,40 +0,0 @@
# Keep A Tally With collections.Counter
Python's `collections` module comes with a
[`Counter`](https://docs.python.org/3/library/collections.html#collections.Counter)
object which is a specialized dict subclass focussed on tallying counts of keys.
> It is a collection where elements are stored as dictionary keys and their
> counts are stored as dictionary values. Counts are allowed to be any integer
> value including zero or negative counts.
I used it recently while doing an exploratory implementation of a Byte-Pair
Encoding (BPE):
```python
from collections import Counter
def get_pair_counts(token_ids: list[int]) -> Counter:
"""Count how often each adjacent pair appears"""
counts = Counter()
for i in range(len(token_ids) - 1):
pair = (token_ids[i], token_ids[i + 1])
counts[pair] += 1
return counts
```
Here I'm able to count the number of occurrences of each pair of bytes from the
input text. A tuple of `int` values is hashable, so they work great as keys for
a `Counter`.
The count value of any key will default to `0`. That makes it straightforward to
increment from there as you iterating over occurrences.
```python
>>> counts = Counter()
>>> counts['hello']
0
>>> count['hello'] += 1
>>> count['hello']
1
```
@@ -1,81 +0,0 @@
# Lint And Format Project With Ruff
[Ruff](https://docs.astral.sh/ruff/) is "an extremely fast Python linter and
code formatter, written in Rust." I recently added it to my [`py-vmt` CLI project](https://github.com/jbranchaud/py-vmt) and here are some of the commands
I used right out of the box.
First, I use `uv` and so I installed it like so:
```bash
uv add --dev ruff
```
First, I checked for linting errors. There were a bunch. The output looked like
this:
```bash
uv run ruff check
F841 Local variable `frozen_datetime` is assigned to but never used
--> tests/src/py_vmt/test_cli.py:88:43
|
86 | 2026, 3, 14, 15, 5, 11, 0, datetime.timezone.utc
87 | )
88 | with freeze_time(initial_datetime) as frozen_datetime:
| ^^^^^^^^^^^^^^^
89 | # cancel session without one started
90 | cancel_result = runner.invoke(cli, ["cancel"])
|
help: Remove assignment to unused variable `frozen_datetime`
...
Found 11 errors.
[*] 2 fixable with the `--fix` option (7 hidden fixes can be enabled with the `--unsafe-fixes` option).
```
Some of them could be automatically fixed, so I dealt with those first using the
`--fix` flag.
```bash
uv run ruff check --fix
```
The remaining lint issues I had to deal with manually. Once I had addressed all
of them I got this message:
```bash
uv run ruff check
All checks passed!
```
With the lint issues out of the way, the next stuff was to use `ruff` to apply
consistent auto-formatting across the entire project. Because I hadn't been
using any auto-formatter up to this point on this project, I can expect the diff
to be significant.
I can start with a dry run using the `--check` flag. This gives a summary of how
much formatting churn there is going to be.
```bash
uv run ruff format --check
Would reformat: src/py_vmt/cli.py
Would reformat: src/py_vmt/session.py
Would reformat: src/py_vmt/time_helpers.py
Would reformat: tests/src/py_vmt/test_cli.py
Would reformat: tests/src/py_vmt/test_session.py
Would reformat: tests/src/py_vmt/test_time_helpers.py
6 files would be reformatted, 3 files already formatted
```
I'm using git and I have a clean working copy, so there is no real harm in just
going for it either.
```bash
uv run ruff format
6 files reformatted, 3 files left unchanged
```
That makes all the formatting changes and I can use `git diff` to browse through
them before eventually committing them.
See `uv run ruff check --help` and `uv run ruff format --help` for more details.

Some files were not shown because too many files have changed in this diff Show More