1
0
mirror of https://github.com/jbranchaud/til synced 2026-01-04 23:58:01 +00:00

Compare commits

52 Commits

Author SHA1 Message Date
jbranchaud
8d8cfd56ce Add Determine Absolute Path Of Top-Level Project Directory as a Git TIL 2026-01-03 16:36:37 -06:00
jbranchaud
f4faa06258 Add another useful link to recent TIL 2026-01-03 12:53:33 -06:00
jbranchaud
8ccbd82320 Add Display Line Numbers While Using Less as a Unix TIL 2026-01-02 18:40:28 -07:00
jbranchaud
5ea4165893 Add Look In Ruby Version Dotfile as a Mise TIL 2026-01-01 16:00:46 -07:00
jbranchaud
73476a8d16 Add Install From Nonstandard Brewfile as a Brew TIL 2026-01-01 12:16:51 -07:00
jbranchaud
c5ce81f918 Update README to reflect current work 2026-01-01 12:06:16 -07:00
jbranchaud
32be787998 Update copyright year to 2026 2026-01-01 11:51:51 -07:00
jbranchaud
1d835d3553 Simplify notes:sync to pull directly into local main
Was fetching remote then checking out stale local branch
2026-01-01 11:51:23 -07:00
copilot-swe-agent[bot]
0d4959046d Reorder commands: commit before pull --rebase
Co-authored-by: jbranchaud <694063+jbranchaud@users.noreply.github.com>
2025-12-31 17:28:33 -07:00
copilot-swe-agent[bot]
b1198d2488 Simplify pull command to use configured upstream
Co-authored-by: jbranchaud <694063+jbranchaud@users.noreply.github.com>
2025-12-31 17:28:33 -07:00
copilot-swe-agent[bot]
6c3805e7cd Add git pull --rebase to notes:push task
Co-authored-by: jbranchaud <694063+jbranchaud@users.noreply.github.com>
2025-12-31 17:28:33 -07:00
jbranchaud
d980514bff Add Create Interactive Picker For Set Of Subtasks as a Taskfile TIL 2025-12-31 12:33:50 -07:00
jbranchaud
db26fc97c6 Add Set Up Forwarding Prefix For Nested Session as a tmux TIL 2025-12-31 12:10:52 -07:00
jbranchaud
8094448877 Add Join URI Path Parts as a Ruby TIL 2025-12-30 10:39:03 -07:00
jbranchaud
883b3e6ee6 Add Run Dev Processes With Overmind Instead Of Foreman as a Rails TIL 2025-12-28 10:21:35 -06:00
jbranchaud
57c4954d6f Add Regenerate Lock File With Newer Bundler as a Ruby TIL 2025-12-26 11:33:07 -06:00
jbranchaud
86a7815a9f Add Specify Default Team And App For Project as a Heroku TIL 2025-12-22 20:46:45 -06:00
jbranchaud
676038e992 Add Create A Module Of Utility Functions as a Ruby TIL 2025-12-17 16:54:43 -06:00
jbranchaud
01fd503a92 Add Run Rails Console With Remote Dokku App as a Rails TIL 2025-12-15 22:27:47 -06:00
jbranchaud
8b718aee4f Add Describe Current Changes And Create New Change as a jj TIL 2025-12-10 11:34:13 -06:00
jbranchaud
88f49de7f3 Add Reference The Full Match In The Replacement as a Sed TIL 2025-12-01 14:07:25 -06:00
jbranchaud
9f9fce7835 Add Make Structs Easier To Use With Keyword Initialization as a Ruby TIL 2025-12-01 06:35:43 -06:00
jbranchaud
65a4d0ef3d Add Rename A Bunch Of Files By Constructing mv Commands as a Unix TIL 2025-11-30 20:10:59 -06:00
jbranchaud
6c8a5eb36d Add new newsletter URL 2025-11-29 15:10:43 -06:00
jbranchaud
fed722d7fe Add Access Your GitHub Profile Photo as a GitHub TIL 2025-11-29 14:40:34 -06:00
jbranchaud
fbebc3e5ee Add Install And Require Gems Inline Without Gemfile as a Ruby TIL 2025-11-28 23:24:00 -06:00
jbranchaud
83d55c420e Add Use .ruby Extension For Template File as a Rails TIL 2025-11-27 10:29:56 -06:00
jbranchaud
8dbbfe0eda Add Create Mock Class That Can Be Overridden as a Ruby TIL 2025-11-25 22:18:00 -06:00
jbranchaud
c38d9f090e Add Monitor Usage Limits From CLI as a Claude Code TIL 2025-11-22 08:43:26 -06:00
jbranchaud
bae3527baf Add Determine ipv4 And ipv6 Public IP Addresses as a Unix TIL 2025-11-21 16:43:08 -06:00
jbranchaud
53a0b88eff Add Get Idea Of What Is In A JSON Column as a MySQL TIL 2025-11-20 09:06:08 -06:00
jbranchaud
665c8f994f Add List All Git Aliases From gitconfig as a Git TIL 2025-11-19 14:39:41 -06:00
jbranchaud
5f11b1665b Remove dprint config to never text wrap
This was causing multi-line sequences of text in TIL markdown files to be
unwrapped into one long line.
2025-11-18 13:20:58 -06:00
jbranchaud
ce5ff038c0 Add Generate A Sequence Of Numbered Items as a Unix TIL 2025-11-18 13:20:49 -06:00
jbranchaud
486a6ef5a9 Add Open Current Prompt In Default Editor as a Claude Code TIL 2025-11-18 12:54:50 -06:00
jbranchaud
c1ce559452 Add Add A Bunch Of CLI Utilities With corutils as a Mac TIL 2025-11-16 22:21:45 -06:00
jbranchaud
07c4aa86b7 Add Squash Changes Into Parent Commit Interactively as a jj TIL 2025-11-16 17:31:42 -06:00
jbranchaud
a0c2a29a96 Add See What Databases You Have Access To as a Planetscale TIL 2025-11-16 10:34:44 -06:00
jbranchaud
45b269abf1 Add Launch Some Confetti as a Mac TIL 2025-11-14 18:52:45 -06:00
jbranchaud
44dc6f2b1f Add Install Go Packages In Brewfile as a Brew TIL 2025-11-14 18:19:43 -06:00
jbranchaud
821a7e5c67 Add Do Project Time Tracking From The CLI as a Workflow TIL 2025-11-14 00:32:13 -06:00
jbranchaud
c0f20267bb Add Shorten SSH Commands With Aliases as a Unix TIL 2025-11-12 15:00:19 -06:00
jbranchaud
50deb6175f Add Detect How Long A User Has Been Idle as a Mac TIL 2025-11-11 17:13:36 -06:00
jbranchaud
d1f41884ce Add Set Default Tasks For Rake To Run as a Ruby TIL 2025-11-10 18:06:12 -06:00
jbranchaud
91149fe7cc Add Show Summary Stats For Current Branch as a Git TIL 2025-11-10 17:25:24 -06:00
jbranchaud
e2eb31a4a9 Add Reference Hash Key With Safe Navigation as a Ruby TIL 2025-11-09 17:12:09 -05:00
jbranchaud
113b7b2dfe Add Prevent Sleep With The Caffeinate Command as a Mac TIL 2025-11-09 13:50:03 -05:00
jbranchaud
16074c021f Add Convert JSON Field To Hash With Indifferent Access as a Rails TIL 2025-11-08 08:55:41 -05:00
jbranchaud
9e76753540 Add Open File On Remote Like GitHub as a VSCode TIL 2025-11-03 14:19:14 -05:00
jbranchaud
eb4dea611e Add Inspect Assertions Preventing Sleep as a Mac TIL 2025-11-02 15:21:31 -06:00
jbranchaud
6ef998b024 Add Check If A File Is Under Version Control as a Git TIL 2025-11-02 11:01:30 -06:00
jbranchaud
6e066ec72a Add Target Another Repo When Creating A PR as a GitHub TIL 2025-11-02 08:18:41 -06:00
46 changed files with 1631 additions and 9 deletions

View File

@@ -6,11 +6,11 @@ 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
pairing with smart people at Hashrocket.
working across different projects via [VisualMode](https://www.visualmode.dev/).
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
_1676 TILs and counting..._
_1719 TILs and counting..._
See some of the other learning resources I work on:
@@ -31,6 +31,7 @@ If you've learned something here, support my efforts writing daily TILs by
* [AWS](#aws)
* [Brew](#brew)
* [Chrome](#chrome)
* [Claude Code](#claude-code)
* [Clojure](#clojure)
* [CSS](#css)
* [Deno](#deno)
@@ -130,6 +131,8 @@ If you've learned something here, support my efforts writing daily TILs by
- [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
@@ -153,6 +156,11 @@ If you've learned something here, support my efforts writing daily TILs by
- [Trigger Commands From The Devtools Command Palette](chrome/trigger-commands-from-the-devtools-command-palette.md)
- [View Network Traffic For New Tabs](chrome/view-network-traffic-for-new-tabs.md)
### Claude Code
- [Monitor Usage Limits From CLI](claude-code/monitor-usage-limits-from-cli.md)
- [Open Current Prompt In Default Editor](claude-code/open-current-prompt-in-default-editor.md)
### Clojure
- [Aggregation Using merge-with](clojure/aggregation-using-merge-with.md)
@@ -322,6 +330,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [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)
@@ -340,6 +349,7 @@ 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)
- [Dropping Commits With Git Rebase](git/dropping-commits-with-git-rebase.md)
@@ -373,6 +383,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Last Commit A File Appeared In](git/last-commit-a-file-appeared-in.md)
- [List All Files Added During Span Of Time](git/list-all-files-added-during-span-of-time.md)
- [List All Files Changed Between Two Branches](git/list-all-files-changed-between-two-branches.md)
- [List All Git Aliases From gitconfig](git/list-all-git-aliases-from-gitconfig.md)
- [List Branches That Contain A Commit](git/list-branches-that-contain-a-commit.md)
- [List Commits On A Branch](git/list-commits-on-a-branch.md)
- [List Different Commits Between Two Branches](git/list-different-commits-between-two-branches.md)
@@ -408,6 +419,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Show File Diffs When Viewing Git Log](git/show-file-diffs-when-viewing-git-log.md)
- [Show List Of Most Recently Committed Branches](git/show-list-of-most-recently-committed-branches.md)
- [Show Only Commits That Touch Specific Lines](git/show-only-commits-that-touch-specific-lines.md)
- [Show Summary Stats For Current Branch](git/show-summary-stats-for-current-branch.md)
- [Show The diffstat Summary Of A Commit](git/show-the-diffstat-summary-of-a-commit.md)
- [Show The Good And The Bad With Git Bisect](git/show-the-good-and-the-bad-with-git-bisect.md)
- [Show What Is In A Stash](git/show-what-is-in-a-stash.md)
@@ -438,7 +450,9 @@ If you've learned something here, support my efforts writing daily TILs by
### GitHub
- [Access Your GitHub Profile Photo](github/access-your-github-profile-photo.md)
- [Open A PR To An Unforked Repo](github/open-a-pr-to-an-unforked-repo.md)
- [Target Another Repo When Creating A PR](github/target-another-repo-when-creating-a-pr.md)
- [Tell gh What The Default Repo Is](github/tell-gh-what-the-default-repo-is.md)
### GitHub Actions
@@ -494,6 +508,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Open Dashboard For Specific Add-On](heroku/open-dashboard-for-specific-add-on.md)
- [Run SQL Against Remote Postgres Database](heroku/run-sql-against-remote-postgres-database.md)
- [Set And Show Heroku Env Variables](heroku/set-and-show-heroku-env-variables.md)
- [Specify Default Team And App For Project](heroku/specify-default-team-and-app-for-project.md)
- [SSH Into Heroku Server Hosting App](heroku/ssh-into-heroku-server-hosting-app.md)
### HTML
@@ -652,7 +667,9 @@ If you've learned something here, support my efforts writing daily TILs by
### jj
- [Colocate jj And git Directories For Project](jj/colocate-jj-and-git-directories-for-project.md)
- [Describe Current Changes And Create New Change](jj/describe-current-changes-and-create-new-change.md)
- [Find System-wide Config File For User](jj/find-system-wide-config-file-for-user.md)
- [Squash Changes Into Parent Commit Interactively](jj/squash-changes-into-parent-commit-interactively.md)
### jq
@@ -694,19 +711,24 @@ If you've learned something here, support my efforts writing daily TILs by
- [Access All Screen And Video Capture Options](mac/access-all-screen-and-video-capture-options.md)
- [Access System Information On OS X](mac/access-system-information-on-osx.md)
- [Access Unsupported Screen Resolutions With RDM](mac/access-unsupported-screen-resolutions-with-rdm.md)
- [Add A Bunch Of CLI Utilities With coreutils](mac/add-a-bunch-of-cli-utilities-with-coreutils.md)
- [Capture Screenshot To Clipboard From CLI](mac/capture-screenshot-to-clipboard-from-cli.md)
- [Check Network Quality Stats From The Command Line](mac/check-network-quality-stats-from-the-command-line.md)
- [Clean Up Old Homebrew Files](mac/clean-up-old-homebrew-files.md)
- [Convert An HEIC Image File To JPG](mac/convert-an-heic-image-file-to-jpg.md)
- [Default Screenshot Location](mac/default-screenshot-location.md)
- [Detect How Long A User Has Been Idle](mac/detect-how-long-a-user-has-been-idle.md)
- [Disable Swipe Navigation For A Specific App](mac/disable-swipe-navigation-for-a-specific-app.md)
- [Display A Message With Alfred](mac/display-a-message-with-alfred.md)
- [Find The Process Using A Specific Port](mac/find-the-process-using-a-specific-port.md)
- [Gesture For Viewing All Windows Of Current App](mac/gesture-for-viewing-all-windows-of-current-app.md)
- [Insert A Non-Breaking Space Character](mac/insert-a-non-breaking-space-character.md)
- [Inspect Assertions Preventing Sleep](mac/inspect-assertions-preventing-sleep.md)
- [Keyboard Shortcuts For Interesting With Text Areas](mac/keyboard-shortcuts-for-interacting-with-text-areas.md)
- [Launch Some Confetti](mac/launch-some-confetti.md)
- [List All The Say Voices](mac/list-all-the-say-voices.md)
- [Open Finder.app To Specific Directory](mac/open-finder-app-to-specific-directory.md)
- [Prevent Sleep With The Caffeinate Command](mac/prevent-sleep-with-the-caffeinate-command.md)
- [Quickly Type En Dashes And Em Dashes](mac/quickly-type-en-dashes-and-em-dashes.md)
- [Require Additional JS Libraries In Postman](mac/require-additional-js-libraries-in-postman.md)
- [Resize App Windows With AppleScript](mac/resize-app-windows-with-applescript.md)
@@ -726,6 +748,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [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)
- [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)
@@ -747,6 +770,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Doing Date Math](mysql/doing-date-math.md)
- [Dump A Database To A File](mysql/dump-a-database-to-a-file.md)
- [Echo A Message From A SQL File](mysql/echo-a-message-from-a-sql-file.md)
- [Get Idea Of What Is In A JSON Column](mysql/get-idea-of-what-is-in-a-json-column.md)
- [Ignore Duplicates When Inserting Records](mysql/ignore-duplicates-when-inserting-records.md)
- [List Databases And Tables](mysql/list-databases-and-tables.md)
- [Run Statements In A Transaction](mysql/run-statements-in-a-transaction.md)
@@ -800,6 +824,7 @@ If you've learned something here, support my efforts writing daily TILs by
### Planetscale
- [See What Databases You Have Access To](planetscale/see-what-databases-you-have-access-to.md)
- [Seed Production Data Into Another Branch](planetscale/seed-production-data-into-another-branch.md)
### pnpm
@@ -1048,6 +1073,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Comparing DateTimes Down To Second Precision](rails/comparing-datetimes-down-to-second-precision.md)
- [Conditional Class Selectors in Haml](rails/conditional-class-selectors-in-haml.md)
- [Convert A Symbol To A Constant](rails/convert-a-symbol-to-a-constant.md)
- [Convert JSON Field To Hash With Indifferent Access](rails/convert-json-field-to-hash-with-indifferent-access.md)
- [Count The Number Of Records By Attribute](rails/count-the-number-of-records-by-attribute.md)
- [Create A Custom Named References Column](rails/create-a-custom-named-references-column.md)
- [Create A Join Table With The Migration DSL](rails/create-a-join-table-with-the-migration-dsl.md)
@@ -1144,6 +1170,8 @@ 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)
@@ -1176,6 +1204,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Update Column Versus Update Attribute](rails/update-column-versus-update-attribute.md)
- [Upgrading Your Manifest For Sprocket's 4](rails/upgrading-your-manifest-for-sprockets-4.md)
- [Use IRB And Ruby Flags With Rails Console](rails/use-irb-and-ruby-flags-with-rails-console.md)
- [Use .ruby Extension For Template File](rails/use-ruby-extension-for-template-file.md)
- [Useful ActiveSupport Constants For Durations](rails/useful-active-support-constants-for-durations.md)
- [Validate Column Data With Check Constraints](rails/validate-column-data-with-check-constraints.md)
- [Verify And Read A Signed Cookie Value](rails/verify-and-read-a-signed-cookie-value.md)
@@ -1337,6 +1366,8 @@ 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)
@@ -1380,15 +1411,18 @@ If you've learned something here, support my efforts writing daily TILs by
- [Iterate With An Offset Index](ruby/iterate-with-an-offset-index.md)
- [Include Extra Context In A Honeybadger Notify](ruby/include-extra-context-in-a-honeybadger-notify.md)
- [Ins And Outs Of Pry](ruby/ins-and-outs-of-pry.md)
- [Install And Require Gems Inline Without Gemfile](ruby/install-and-require-gems-inline-without-gemfile.md)
- [Install Latest Version Of Ruby With asdf](ruby/install-latest-version-of-ruby-with-asdf.md)
- [Invoking Rake Tasks Multiple Times](ruby/invoking-rake-tasks-multiple-times.md)
- [IRB Has Built-In Benchmarking With Ruby 3](ruby/irb-has-built-in-benchmarking-with-ruby-3.md)
- [Join URI Path Parts](ruby/join-uri-path-parts.md)
- [Jump Out Of A Nested Context With Throw/Catch](ruby/jump-out-of-a-nested-context-with-throw-catch.md)
- [Last Raised Exception In The Call Stack](ruby/last-raised-exception-in-the-call-stack.md)
- [Limit Split](ruby/limit-split.md)
- [List The Running Ruby Version](ruby/list-the-running-ruby-version.md)
- [Listing Local Variables](ruby/listing-local-variables.md)
- [Make An Executable Ruby Script](ruby/make-an-executable-ruby-script.md)
- [Make Structs Easier To Use With Keyword Initialization](ruby/make-structs-easier-to-use-with-keyword-initialization.md)
- [Map With Index Over An Array](ruby/map-with-index-over-an-array.md)
- [Mock Method Chain Calls With RSpec](ruby/mock-method-chain-calls-with-rspec.md)
- [Mocking Requests With Partial URIs Using Regex](ruby/mocking-requests-with-partial-uris-using-regex.md)
@@ -1415,6 +1449,8 @@ If you've learned something here, support my efforts writing daily TILs by
- [Rake Only Lists Tasks With Descriptions](ruby/rake-only-lists-tasks-with-descriptions.md)
- [Read The First Line From A File](ruby/read-the-first-line-from-a-file.md)
- [Refer To Implicit Block Argument With It](ruby/refer-to-implicit-block-argument-with-it.md)
- [Reference Hash Key With Safe Navigation](ruby/reference-hash-key-with-safe-navigation.md)
- [Regenerate Lock File With Newer Bundler](ruby/regenerate-lock-file-with-newer-bundler.md)
- [Rendering ERB](ruby/rendering-erb.md)
- [Replace The Current Process With An External Command](ruby/replace-the-current-process-with-an-external-command.md)
- [Require Entire Gemfile In Pry Session](ruby/require-entire-gemfile-in-pry-session.md)
@@ -1429,6 +1465,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Scripting With RVM](ruby/scripting-with-rvm.md)
- [Scroll To Top Of Page With Capybara](ruby/scroll-to-top-of-page-with-capybara.md)
- [Search For Gem Versions Available To Install](ruby/search-for-gem-versions-available-to-install.md)
- [Set Default Tasks For Rake To Run](ruby/set-default-tasks-for-rake-to-run.md)
- [Set RVM Default Ruby](ruby/set-rvm-default-ruby.md)
- [Shift The Month On A Date Object](ruby/shift-the-month-on-a-date-object.md)
- [Show Public Methods With Pry](ruby/show-public-methods-with-pry.md)
@@ -1473,6 +1510,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [OSX sed Does Regex A Bit Different](sed/osx-sed-does-regex-a-bit-different.md)
- [Output Only Lines Involved In A Substitution](sed/output-only-lines-involved-in-a-substitution.md)
- [Reference A Capture In The Regex](sed/reference-a-capture-in-the-regex.md)
- [Reference The Full Match In The Replacement](sed/reference-the-full-match-in-the-replacement.md)
- [Use An Alternative Delimiter In A Substitution](sed/use-an-alternative-delimiter-in-a-substitution.md)
### Shell
@@ -1499,6 +1537,7 @@ If you've learned something here, support my efforts writing daily TILs by
### Taskfile
- [Create Interactive Picker For Set Of Subtasks](taskfile/create-interactive-picker-for-set-of-subtasks.md)
- [Run A Task If It Meets Criteria](taskfile/run-a-task-if-it-meets-criteria.md)
### tmux
@@ -1534,6 +1573,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Reset An Option Back To Its Default Value](tmux/reset-an-option-back-to-its-default-value.md)
- [Set Environment Variables When Creating Session](tmux/set-environment-variables-when-creating-session.md)
- [Set Session Specific Environment Variables](tmux/set-session-specific-environment-variables.md)
- [Set Up Forwarding Prefix For Nested Session](tmux/set-up-forwarding-prefix-for-nested-session.md)
- [Show The Current Value For An Option](tmux/show-the-current-value-for-an-option.md)
- [Swap Split Panes](tmux/swap-split-panes.md)
- [Switch To A Specific Session And Window](tmux/switch-to-a-specific-session-and-window.md)
@@ -1596,9 +1636,11 @@ If you've learned something here, support my efforts writing daily TILs by
- [Curl With Cookies](unix/curl-with-cookies.md)
- [Curling For Headers](unix/curling-for-headers.md)
- [Curling With Basic Auth Credentials](unix/curling-with-basic-auth-credentials.md)
- [Determine ipv4 And ipv6 Public IP Addresses](unix/determine-ipv4-and-ipv6-public-ip-addresses.md)
- [Different Ways To Generate A v4 UUID](unix/different-ways-to-generate-a-v4-uuid.md)
- [Display All The Terminal Colors](unix/display-all-the-terminal-colors.md)
- [Display Free Disk Space](unix/display-free-disk-space.md)
- [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)
@@ -1626,6 +1668,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Fix Unlinked Node Binaries With asdf](unix/fix-unlinked-node-binaries-with-asdf.md)
- [Forward Multiple Ports Over SSH](unix/forward-multiple-ports-over-ssh.md)
- [Generate A SAML Key And Certificate Pair](unix/generate-a-saml-key-and-certificate-pair.md)
- [Generate A Sequence Of Numbered Items](unix/generate-a-sequence-of-numbered-items.md)
- [Generate Base64 Encoding Without Newlines](unix/generate-base64-encoding-without-newlines.md)
- [Generate Random 20-Character Hex String](unix/generate-random-20-character-hex-string.md)
- [Get A List Of Locales On Your System](unix/get-a-list-of-locales-on-your-system.md)
@@ -1688,6 +1731,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Produce A Lowercase V4 UUID](unix/produce-a-lowercase-v4-uuid.md)
- [Provide A Fallback Value For Unset Parameter](unix/provide-a-fallback-value-for-unset-parameter.md)
- [Remove A Directory Called `-p`](unix/remove-a-directory-called-dash-p.md)
- [Rename A Bunch Of Files By Constructing mv Commands](unix/rename-a-bunch-of-files-by-constructing-mv-commands.md)
- [Repeat Yourself](unix/repeat-yourself.md)
- [Replace Pattern Across Many Files In A Project](unix/replace-pattern-across-many-files-in-a-project.md)
- [Run A Command Repeatedly Several Times](unix/run-a-command-repeatedly-several-times.md)
@@ -1701,6 +1745,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Securely Remove Files](unix/securely-remove-files.md)
- [See Where asdf Gets Current Tool Version](unix/see-where-asdf-gets-current-tool-version.md)
- [Set The asdf Package Version For A Single Shell](unix/set-the-asdf-package-version-for-a-single-shell.md)
- [Shorten SSH Commands With Aliases](unix/shorten-ssh-commands-with-aliases.md)
- [Show A File Preview When Searching With FZF](unix/show-a-file-preview-when-searching-with-fzf.md)
- [Show Disk Usage For The Current Directory](unix/show-disk-usage-for-the-current-directory.md)
- [Show The Size Of Everything In A Directory](unix/show-the-size-of-everything-in-a-directory.md)
@@ -1906,6 +1951,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Find The Location Of User Settings JSON File](vscode/find-the-location-of-user-settings-json-file.md)
- [Jump To Problems In The Current File](vscode/jump-to-problems-in-the-current-file.md)
- [Open An Integrated Terminal Window](vscode/open-an-integrated-terminal-window.md)
- [Open File On Remote Like GitHub](vscode/open-file-on-remote-like-github.md)
- [Pop Open The Quick Fix Window](vscode/pop-open-the-quick-fix-window.md)
- [Step Through Project-Wide Search Results](vscode/step-through-project-wide-search-results.md)
- [Synchronize Vim Clipboard With System Clipboard](vscode/synchronize-vim-clipboard-with-system-clipboard.md)
@@ -1935,6 +1981,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Create A Local Sanity Dataset Backup](workflow/create-a-local-sanity-dataset-backup.md)
- [Create A Public URL For A Local Server](workflow/create-a-public-url-for-a-local-server.md)
- [Create Todo Items In Logseq](workflow/create-todo-items-in-logseq.md)
- [Do Project Time Tracking From The CLI](workflow/do-project-time-tracking-from-the-cli.md)
- [Enable Dev Tools For Safari](workflow/enable-dev-tools-for-safari.md)
- [Forward Stripe Events To Local Server](workflow/forward-stripe-events-to-local-server.md)
- [Get URL For GitHub User Profile Photo](workflow/get-url-for-github-user-profile-photo.md)
@@ -2013,7 +2060,7 @@ I shamelessly stole this idea from
## License
&copy; 2015-2025 Josh Branchaud
&copy; 2015-2026 Josh Branchaud
This repository is licensed under the MIT license. See `LICENSE` for
details.

View File

@@ -32,8 +32,7 @@ tasks:
notes:sync:
desc: Sync latest changes from the notes submodule
cmds:
- git submodule update --remote {{.NOTES_DIR}}
- cd {{.NOTES_DIR}} && git checkout main
- cd {{.NOTES_DIR}} && git checkout main && git pull
silent: false
notes:open:
@@ -49,6 +48,7 @@ tasks:
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

View File

@@ -0,0 +1,25 @@
# 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,39 @@
# 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
# 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.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,27 @@
# Display Line Numbers While Using Less
Including line numbers while viewing files with `less` can provide useful
context for understanding where you are within the file. This is especially true
if you've used `&` to filter down to lines that match a pattern.
You can start `less` with line numbers with the `-N` flag (or `--LINE-NUMBERS`
if you really want to spell it out).
```bash
$ less -N log/development.log
```
If you've already started up `less` and wish you had included line numbers,
there is no reason to restart it with the flag. Instead, toggle the line numbers
option on within the `less` process. To do this, type `-N`. It will prompt you
with `Constantly display line numbers (press RETURN)`. Hit enter and line
numbers will appear to the left of each line in the file.
Similarly, to toggle line numbers back off within `less`, hit `-n` (lower-case
`n`), accept the prompt, and back off they go.
Both of these (`-N`/`-n`) are options being set (toggled) via the `-` command.
There are many other options like these that can be configured within a `less`
session in the same way.
See `man less` and find the `-` command and the available `OPTIONS`.

View File

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

View File

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

View File

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

View File

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

View File

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