mirror of
https://github.com/jbranchaud/til
synced 2026-01-04 23:58:01 +00:00
Compare commits
52 Commits
060ce8262d
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d8cfd56ce | ||
|
|
f4faa06258 | ||
|
|
8ccbd82320 | ||
|
|
5ea4165893 | ||
|
|
73476a8d16 | ||
|
|
c5ce81f918 | ||
|
|
32be787998 | ||
|
|
1d835d3553 | ||
|
|
0d4959046d | ||
|
|
b1198d2488 | ||
|
|
6c3805e7cd | ||
|
|
d980514bff | ||
|
|
db26fc97c6 | ||
|
|
8094448877 | ||
|
|
883b3e6ee6 | ||
|
|
57c4954d6f | ||
|
|
86a7815a9f | ||
|
|
676038e992 | ||
|
|
01fd503a92 | ||
|
|
8b718aee4f | ||
|
|
88f49de7f3 | ||
|
|
9f9fce7835 | ||
|
|
65a4d0ef3d | ||
|
|
6c8a5eb36d | ||
|
|
fed722d7fe | ||
|
|
fbebc3e5ee | ||
|
|
83d55c420e | ||
|
|
8dbbfe0eda | ||
|
|
c38d9f090e | ||
|
|
bae3527baf | ||
|
|
53a0b88eff | ||
|
|
665c8f994f | ||
|
|
5f11b1665b | ||
|
|
ce5ff038c0 | ||
|
|
486a6ef5a9 | ||
|
|
c1ce559452 | ||
|
|
07c4aa86b7 | ||
|
|
a0c2a29a96 | ||
|
|
45b269abf1 | ||
|
|
44dc6f2b1f | ||
|
|
821a7e5c67 | ||
|
|
c0f20267bb | ||
|
|
50deb6175f | ||
|
|
d1f41884ce | ||
|
|
91149fe7cc | ||
|
|
e2eb31a4a9 | ||
|
|
113b7b2dfe | ||
|
|
16074c021f | ||
|
|
9e76753540 | ||
|
|
eb4dea611e | ||
|
|
6ef998b024 | ||
|
|
6e066ec72a |
55
README.md
55
README.md
@@ -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
|
||||
|
||||
© 2015-2025 Josh Branchaud
|
||||
© 2015-2026 Josh Branchaud
|
||||
|
||||
This repository is licensed under the MIT license. See `LICENSE` for
|
||||
details.
|
||||
|
||||
@@ -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
|
||||
|
||||
25
brew/install-from-nonstandard-brewfile.md
Normal file
25
brew/install-from-nonstandard-brewfile.md
Normal 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.
|
||||
27
brew/install-go-packages-in-brewfile.md
Normal file
27
brew/install-go-packages-in-brewfile.md
Normal 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.
|
||||
18
claude-code/monitor-usage-limits-from-cli.md
Normal file
18
claude-code/monitor-usage-limits-from-cli.md
Normal 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.
|
||||
15
claude-code/open-current-prompt-in-default-editor.md
Normal file
15
claude-code/open-current-prompt-in-default-editor.md
Normal 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.
|
||||
@@ -1,7 +1,4 @@
|
||||
{
|
||||
"excludes": ["README.md"],
|
||||
"markdown": {
|
||||
"textWrap": "never"
|
||||
},
|
||||
"plugins": ["https://plugins.dprint.dev/markdown-0.16.0.wasm"]
|
||||
}
|
||||
|
||||
36
git/check-if-a-file-is-under-version-control.md
Normal file
36
git/check-if-a-file-is-under-version-control.md
Normal 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.
|
||||
@@ -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.
|
||||
28
git/list-all-git-aliases-from-gitconfig.md
Normal file
28
git/list-all-git-aliases-from-gitconfig.md
Normal 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.
|
||||
26
git/show-summary-stats-for-current-branch.md
Normal file
26
git/show-summary-stats-for-current-branch.md
Normal 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.
|
||||
25
github/access-your-github-profile-photo.md
Normal file
25
github/access-your-github-profile-photo.md
Normal 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)
|
||||
20
github/target-another-repo-when-creating-a-pr.md
Normal file
20
github/target-another-repo-when-creating-a-pr.md
Normal 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.
|
||||
34
heroku/specify-default-team-and-app-for-project.md
Normal file
34
heroku/specify-default-team-and-app-for-project.md
Normal 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.
|
||||
28
jj/describe-current-changes-and-create-new-change.md
Normal file
28
jj/describe-current-changes-and-create-new-change.md
Normal 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.
|
||||
31
jj/squash-changes-into-parent-commit-interactively.md
Normal file
31
jj/squash-changes-into-parent-commit-interactively.md
Normal 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)
|
||||
51
mac/add-a-bunch-of-cli-utilities-with-coreutils.md
Normal file
51
mac/add-a-bunch-of-cli-utilities-with-coreutils.md
Normal 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.
|
||||
39
mac/detect-how-long-a-user-has-been-idle.md
Normal file
39
mac/detect-how-long-a-user-has-been-idle.md
Normal 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.
|
||||
41
mac/inspect-assertions-preventing-sleep.md
Normal file
41
mac/inspect-assertions-preventing-sleep.md
Normal 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.
|
||||
36
mac/launch-some-confetti.md
Normal file
36
mac/launch-some-confetti.md
Normal 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
|
||||
```
|
||||
30
mac/prevent-sleep-with-the-caffeinate-command.md
Normal file
30
mac/prevent-sleep-with-the-caffeinate-command.md
Normal 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.
|
||||
27
mise/look-in-ruby-version-dotfile.md
Normal file
27
mise/look-in-ruby-version-dotfile.md
Normal 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.
|
||||
40
mysql/get-idea-of-what-is-in-a-json-column.md
Normal file
40
mysql/get-idea-of-what-is-in-a-json-column.md
Normal 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;
|
||||
```
|
||||
23
planetscale/see-what-databases-you-have-access-to.md
Normal file
23
planetscale/see-what-databases-you-have-access-to.md
Normal 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.
|
||||
36
rails/convert-json-field-to-hash-with-indifferent-access.md
Normal file
36
rails/convert-json-field-to-hash-with-indifferent-access.md
Normal 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.
|
||||
46
rails/run-dev-processes-with-overmind-instead-of-foreman.md
Normal file
46
rails/run-dev-processes-with-overmind-instead-of-foreman.md
Normal 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.
|
||||
49
rails/run-rails-console-with-remote-dokku-app.md
Normal file
49
rails/run-rails-console-with-remote-dokku-app.md
Normal 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 "$@"
|
||||
```
|
||||
42
rails/use-ruby-extension-for-template-file.md
Normal file
42
rails/use-ruby-extension-for-template-file.md
Normal 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.
|
||||
59
ruby/create-a-module-of-utility-functions.md
Normal file
59
ruby/create-a-module-of-utility-functions.md
Normal 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.
|
||||
85
ruby/create-mock-class-that-can-be-overridden.md
Normal file
85
ruby/create-mock-class-that-can-be-overridden.md
Normal 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.
|
||||
74
ruby/install-and-require-gems-inline-without-gemfile.md
Normal file
74
ruby/install-and-require-gems-inline-without-gemfile.md
Normal 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)
|
||||
43
ruby/join-uri-path-parts.md
Normal file
43
ruby/join-uri-path-parts.md
Normal 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.
|
||||
@@ -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)
|
||||
30
ruby/reference-hash-key-with-safe-navigation.md
Normal file
30
ruby/reference-hash-key-with-safe-navigation.md
Normal 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)
|
||||
```
|
||||
34
ruby/regenerate-lock-file-with-newer-bundler.md
Normal file
34
ruby/regenerate-lock-file-with-newer-bundler.md
Normal 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.
|
||||
32
ruby/set-default-tasks-for-rake-to-run.md
Normal file
32
ruby/set-default-tasks-for-rake-to-run.md
Normal 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
|
||||
```
|
||||
27
sed/reference-the-full-match-in-the-replacement.md
Normal file
27
sed/reference-the-full-match-in-the-replacement.md
Normal 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>/`).
|
||||
74
taskfile/create-interactive-picker-for-set-of-subtasks.md
Normal file
74
taskfile/create-interactive-picker-for-set-of-subtasks.md
Normal 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.
|
||||
24
tmux/set-up-forwarding-prefix-for-nested-session.md
Normal file
24
tmux/set-up-forwarding-prefix-for-nested-session.md
Normal 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`.
|
||||
29
unix/determine-ipv4-and-ipv6-public-ip-addresses.md
Normal file
29
unix/determine-ipv4-and-ipv6-public-ip-addresses.md
Normal 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.
|
||||
27
unix/display-line-numbers-while-using-less.md
Normal file
27
unix/display-line-numbers-while-using-less.md
Normal 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`.
|
||||
39
unix/generate-a-sequence-of-numbered-items.md
Normal file
39
unix/generate-a-sequence-of-numbered-items.md
Normal 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.
|
||||
51
unix/rename-a-bunch-of-files-by-constructing-mv-commands.md
Normal file
51
unix/rename-a-bunch-of-files-by-constructing-mv-commands.md
Normal 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
|
||||
```
|
||||
35
unix/shorten-ssh-commands-with-aliases.md
Normal file
35
unix/shorten-ssh-commands-with-aliases.md
Normal 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`.
|
||||
24
vscode/open-file-on-remote-like-github.md
Normal file
24
vscode/open-file-on-remote-like-github.md
Normal 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.
|
||||
35
workflow/do-project-time-tracking-from-the-cli.md
Normal file
35
workflow/do-project-time-tracking-from-the-cli.md
Normal 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.
|
||||
Reference in New Issue
Block a user