mirror of
https://github.com/jbranchaud/til
synced 2026-01-06 00:28:01 +00:00
Compare commits
123 Commits
ab36b2e467
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bd021f7eab | ||
|
|
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 | ||
|
|
060ce8262d | ||
|
|
96fd138837 | ||
|
|
a51d716e45 | ||
|
|
59de2fef0d | ||
|
|
fdd2461b75 | ||
|
|
e8c2e01d6f | ||
|
|
ed9cedc870 | ||
|
|
da585ec5a4 | ||
|
|
35d1a81ea7 | ||
|
|
d69fefe9f0 | ||
|
|
1cc612294e | ||
|
|
d79264395b | ||
|
|
2d5abd9cbf | ||
|
|
2efaf27066 | ||
|
|
6b4b2c588c | ||
|
|
e473fa781d | ||
|
|
5ce5eccb0a | ||
|
|
db4961a8eb | ||
|
|
ff227a39ed | ||
|
|
0d3975eb9c | ||
|
|
d171c3784b | ||
|
|
e6d00a94f3 | ||
|
|
0e934d8dd3 | ||
|
|
c30b17dd68 | ||
|
|
757e163c2e | ||
|
|
cf037f13f7 | ||
|
|
08fb235e81 | ||
|
|
95dc00d748 | ||
|
|
ece12aac76 | ||
|
|
9a6a40bdd6 | ||
|
|
4b4bd2350f | ||
|
|
5924edf4c0 | ||
|
|
5eb21b3aa2 | ||
|
|
5b3f1536fd | ||
|
|
ec0e84664f | ||
|
|
3912276599 | ||
|
|
d166ffac0b | ||
|
|
e8b953ba6d | ||
|
|
8613c21f41 | ||
|
|
2b5df03981 | ||
|
|
aef15d53b0 | ||
|
|
0c31fb6363 | ||
|
|
cb94142042 | ||
|
|
ae2974e3b8 | ||
|
|
0ed4d84bc6 | ||
|
|
3b7e3258fe | ||
|
|
d00796b054 | ||
|
|
8fecb0e863 | ||
|
|
14942c20d7 | ||
|
|
e901ae3b77 | ||
|
|
a4fee08596 | ||
|
|
6e518763c7 | ||
|
|
bb40353512 | ||
|
|
917f9e516e | ||
|
|
d8dfcce0fc | ||
|
|
0d173ccaaf | ||
|
|
8dd9f86b80 | ||
|
|
2bb8af2880 | ||
|
|
e16c2525be | ||
|
|
a55fff68e1 | ||
|
|
162a7ceea3 | ||
|
|
f578727349 | ||
|
|
4ba53dca7d | ||
|
|
571f465fe6 | ||
|
|
a547b9cee2 | ||
|
|
99ce5aee7b | ||
|
|
60b6aa40ad | ||
|
|
f97634a61e | ||
|
|
34ba60d313 | ||
|
|
3a178e901e |
5
.gitmodules
vendored
Normal file
5
.gitmodules
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
[submodule "notes"]
|
||||
path = notes
|
||||
url = git@github.com:jbranchaud/til-notes-private.git
|
||||
branch = main
|
||||
ignore = all
|
||||
123
README.md
123
README.md
@@ -6,13 +6,15 @@ 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).
|
||||
|
||||
_1628 TILs and counting..._
|
||||
_1720 TILs and counting..._
|
||||
|
||||
See some of the other learning resources I work on:
|
||||
|
||||
- [Get Started with Vimium](https://egghead.io/courses/get-started-with-vimium~3t5f7)
|
||||
- [Ruby Operator Lookup](https://www.visualmode.dev/ruby-operators)
|
||||
- [Vim Un-Alphabet](https://www.youtube.com/playlist?list=PL46-cKSxMYYCMpzXo6p0Cof8hJInYgohU)
|
||||
|
||||
@@ -29,6 +31,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
* [AWS](#aws)
|
||||
* [Brew](#brew)
|
||||
* [Chrome](#chrome)
|
||||
* [Claude Code](#claude-code)
|
||||
* [Clojure](#clojure)
|
||||
* [CSS](#css)
|
||||
* [Deno](#deno)
|
||||
@@ -38,6 +41,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
* [Elixir](#elixir)
|
||||
* [Gatsby](#gatsby)
|
||||
* [Git](#git)
|
||||
* [GitHub](#github)
|
||||
* [GitHub Actions](#github-actions)
|
||||
* [Go](#go)
|
||||
* [GROQ](#groq)
|
||||
@@ -80,6 +84,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
* [SQLite](#sqlite)
|
||||
* [Streaming](#streaming)
|
||||
* [Tailwind CSS](#tailwind-css)
|
||||
* [Taskfile](#taskfile)
|
||||
* [tmux](#tmux)
|
||||
* [TypeScript](#typescript)
|
||||
* [Unix](#unix)
|
||||
@@ -114,6 +119,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
|
||||
- [AWS CLI Requires Groff Executable](aws/aws-cli-requires-groff-executable.md)
|
||||
- [Find And Follow Server Logs](aws/find-and-follow-server-logs.md)
|
||||
- [List RDS Snapshots With Matching Identifier Prefix](aws/list-rds-snapshots-with-matching-identifier-prefix.md)
|
||||
- [Output CLI Results In Different Formats](aws/output-cli-results-in-different-formats.md)
|
||||
- [Sign Up User With Email And Password](aws/sign-up-user-with-email-and-password.md)
|
||||
- [SSH Into An ECS Container](aws/ssh-into-an-ecs-container.md)
|
||||
@@ -122,8 +128,11 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
|
||||
### Brew
|
||||
|
||||
- [Clean Up Your Brew Installations](brew/clean-up-your-brew-installations.md)
|
||||
- [Configure Brew Environment Variables](brew/configure-brew-environment-variables.md)
|
||||
- [Export List Of Everything Installed By Brew](brew/export-list-of-everything-installed-by-brew.md)
|
||||
- [Install 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
|
||||
@@ -134,6 +143,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Duplicate The Current Tab](chrome/duplicate-the-current-tab.md)
|
||||
- [Easier Access To Network Throttling Controls](chrome/easier-access-to-network-throttling-controls.md)
|
||||
- [Keybinding To Focus The Address Bar](chrome/keybinding-to-focus-the-address-bar.md)
|
||||
- [Open Current Tab In New Window With Vimium](chrome/open-current-tab-in-new-window-with-vimium.md)
|
||||
- [Pause JavaScript From The Source DevTools Panel](chrome/pause-javascript-from-the-source-devtools-panel.md)
|
||||
- [Navigate The Browser History With Vimium](chrome/navigate-the-browser-history-with-vimium.md)
|
||||
- [Pretty Print Tabular Data](chrome/pretty-print-tabular-data.md)
|
||||
@@ -146,6 +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)
|
||||
@@ -199,6 +214,10 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Style A Background With A Linear Gradient](css/style-a-background-with-a-linear-gradient.md)
|
||||
- [Using Maps In SCSS](css/using-maps-in-scss.md)
|
||||
|
||||
### Cursor
|
||||
|
||||
- [Allow Cursor To Be Launched From CLI](cursor/allow-cursor-to-be-launched-from-cli.md)
|
||||
|
||||
### Deno
|
||||
|
||||
- [Read In The Contents Of A File](deno/read-in-the-contents-of-a-file.md)
|
||||
@@ -310,13 +329,17 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Caching Credentials](git/caching-credentials.md)
|
||||
- [Change The Start Point Of A Branch](git/change-the-start-point-of-a-branch.md)
|
||||
- [Check How A File Is Being Ignored](git/check-how-a-file-is-being-ignored.md)
|
||||
- [Check If A File Has Changed In A Script](git/check-if-a-file-has-changed-in-a-script.md)
|
||||
- [Check If A File Is Under Version Control](git/check-if-a-file-is-under-version-control.md)
|
||||
- [Checking Commit Ancestry](git/checking-commit-ancestry.md)
|
||||
- [Checkout Old Version Of A File](git/checkout-old-version-of-a-file.md)
|
||||
- [Checkout Previous Branch](git/checkout-previous-branch.md)
|
||||
- [Cherry Pick A Range Of Commits](git/cherry-pick-a-range-of-commits.md)
|
||||
- [Cherry Pick Multiple Commits At Once](git/cherry-pick-multiple-commits-at-once.md)
|
||||
- [Clean Out All Local Branches](git/clean-out-all-local-branches.md)
|
||||
- [Clean Out Working Copy With Patched Restore](git/clean-out-working-copy-with-patched-restore.md)
|
||||
- [Clean Up Old Remote Tracking References](git/clean-up-old-remote-tracking-references.md)
|
||||
- [Clear Entries From Git Stash](git/clear-entries-from-git-stash.md)
|
||||
- [Clone A Repo Just For The Files, Without History](git/clone-a-repo-just-for-the-files-without-history.md)
|
||||
- [Clone A Repo Locally From .git](git/clone-a-repo-locally-from-git.md)
|
||||
- [Configure Global gitignore File](git/configure-global-gitignore-file.md)
|
||||
@@ -326,11 +349,13 @@ 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)
|
||||
- [Dry Runs in Git](git/dry-runs-in-git.md)
|
||||
- [Exclude A File From A Diff Output](git/exclude-a-file-from-a-diff-output.md)
|
||||
- [Exclude A Directory During A Command](git/exclude-a-directory-during-a-command.md)
|
||||
- [Excluding Files Locally](git/excluding-files-locally.md)
|
||||
- [Extend Git With Custom Commands](git/extend-git-with-custom-commands.md)
|
||||
- [Files With Local Changes Cannot Be Removed](git/files-with-local-changes-cannot-be-removed.md)
|
||||
@@ -345,6 +370,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Grep For A Pattern On Another Branch](git/grep-for-a-pattern-on-another-branch.md)
|
||||
- [Grep Over Commit Messages](git/grep-over-commit-messages.md)
|
||||
- [Highlight Extra Whitespace In Diff Output](git/highlight-extra-whitespace-in-diff-output.md)
|
||||
- [Highlight Small Change On Single Line](git/highlight-small-change-on-single-line.md)
|
||||
- [Ignore Changes To A Tracked File](git/ignore-changes-to-a-tracked-file.md)
|
||||
- [Ignore Files Specific To Your Workflow](git/ignore-files-specific-to-your-workflow.md)
|
||||
- [Include A Message With Your Stashed Changes](git/include-a-message-with-your-stashed-changes.md)
|
||||
@@ -357,6 +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)
|
||||
@@ -379,10 +406,12 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Renaming A Branch](git/renaming-a-branch.md)
|
||||
- [Resetting A Reset](git/resetting-a-reset.md)
|
||||
- [Resolve A Merge Conflict From Stash Pop](git/resolve-a-merge-conflict-from-stash-pop.md)
|
||||
- [Restore File From One Branch To The Current](git/restore-file-from-one-branch-to-the-current.md)
|
||||
- [Review Commits From Before A Certain Date](git/review-commits-from-before-a-certain-date.md)
|
||||
- [Run A Git Command From Outside The Repo](git/run-a-git-command-from-outside-the-repo.md)
|
||||
- [Set A Custom Pager For A Specific Command](git/set-a-custom-pager-for-a-specific-command.md)
|
||||
- [Set Default Branch Name For New Repos](git/set-default-branch-name-for-new-repos.md)
|
||||
- [Set Up GPG Signing Key](git/set-up-gpg-signing-key.md)
|
||||
- [Shorthand To Force Push A Branch](git/shorthand-to-force-push-a-branch.md)
|
||||
- [Show All Commits For A File Beyond Renaming](git/show-all-commits-for-a-file-beyond-renaming.md)
|
||||
- [Show Changes For Files That Match A Pattern](git/show-changes-for-files-that-match-a-pattern.md)
|
||||
@@ -390,6 +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)
|
||||
@@ -418,6 +448,13 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [What Is The Current Branch?](git/what-is-the-current-branch.md)
|
||||
- [Whitespace Warnings](git/whitespace-warnings.md)
|
||||
|
||||
### GitHub
|
||||
|
||||
- [Access Your GitHub Profile Photo](github/access-your-github-profile-photo.md)
|
||||
- [Open A PR To An Unforked Repo](github/open-a-pr-to-an-unforked-repo.md)
|
||||
- [Target Another Repo When Creating A PR](github/target-another-repo-when-creating-a-pr.md)
|
||||
- [Tell gh What The Default Repo Is](github/tell-gh-what-the-default-repo-is.md)
|
||||
|
||||
### GitHub Actions
|
||||
|
||||
- [Cache Playwright Dependencies Across Workflows](github-actions/cache-playwright-dependencies-across-workflows.md)
|
||||
@@ -465,17 +502,20 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
|
||||
### Heroku
|
||||
|
||||
- [Check Ruby Version For Production App](heroku/check-ruby-version-for-production-app.md)
|
||||
- [Connect To A Database By Color](heroku/connect-to-a-database-by-color.md)
|
||||
- [Deploy A Review App To A Different Stack](heroku/deploy-a-review-app-to-a-different-stack.md)
|
||||
- [Diagnose Problems In A Heroku Postgres Database](heroku/diagnose-problems-in-a-heroku-postgres-database.md)
|
||||
- [Open Dashboard For Specific Add-On](heroku/open-dashboard-for-specific-add-on.md)
|
||||
- [Run SQL Against Remote Postgres Database](heroku/run-sql-against-remote-postgres-database.md)
|
||||
- [Set And Show Heroku Env Variables](heroku/set-and-show-heroku-env-variables.md)
|
||||
- [Specify Default Team And App For Project](heroku/specify-default-team-and-app-for-project.md)
|
||||
- [SSH Into Heroku Server Hosting App](heroku/ssh-into-heroku-server-hosting-app.md)
|
||||
|
||||
### HTML
|
||||
|
||||
- [Adding Alt Text To An Image](html/adding-alt-text-to-an-image.md)
|
||||
- [Allow Number Input To Accept Decimal Values](html/allow-number-input-to-accept-decimal-values.md)
|
||||
- [Determine Which Button Submitted The Form](html/determine-which-button-submitted-the-form.md)
|
||||
- [Disable Auto-Completion For A Form Input](html/disable-auto-completion-for-a-form-input.md)
|
||||
- [Disclose Additional Details](html/disclose-additional-details.md)
|
||||
@@ -554,6 +594,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Find Where Yarn Is Installing Binaries](javascript/find-where-yarn-is-installing-binaries.md)
|
||||
- [for...in Iterates Over Object Properties](javascript/for-in-iterates-over-object-properties.md)
|
||||
- [Format A Decimal To A Fixed Number Of Digits](javascript/format-a-decimal-to-a-fixed-number-of-digits.md)
|
||||
- [Format A List Of Items By Locale](javascript/format-a-list-of-items-by-locale.md)
|
||||
- [Format Time Zone Identifier](javascript/format-time-zone-identifier.md)
|
||||
- [Formatting Values With Units For Display](javascript/formatting-values-with-units-for-display.md)
|
||||
- [Freeze An Object, Sorta](javascript/freeze-an-object-sorta.md)
|
||||
@@ -627,7 +668,9 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
### jj
|
||||
|
||||
- [Colocate jj And git Directories For Project](jj/colocate-jj-and-git-directories-for-project.md)
|
||||
- [Describe Current Changes And Create New Change](jj/describe-current-changes-and-create-new-change.md)
|
||||
- [Find System-wide Config File For User](jj/find-system-wide-config-file-for-user.md)
|
||||
- [Squash Changes Into Parent Commit Interactively](jj/squash-changes-into-parent-commit-interactively.md)
|
||||
|
||||
### jq
|
||||
|
||||
@@ -669,18 +712,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)
|
||||
@@ -700,6 +749,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)
|
||||
@@ -721,6 +771,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Doing Date Math](mysql/doing-date-math.md)
|
||||
- [Dump A Database To A File](mysql/dump-a-database-to-a-file.md)
|
||||
- [Echo A Message From A SQL File](mysql/echo-a-message-from-a-sql-file.md)
|
||||
- [Get Idea Of What Is In A JSON Column](mysql/get-idea-of-what-is-in-a-json-column.md)
|
||||
- [Ignore Duplicates When Inserting Records](mysql/ignore-duplicates-when-inserting-records.md)
|
||||
- [List Databases And Tables](mysql/list-databases-and-tables.md)
|
||||
- [Run Statements In A Transaction](mysql/run-statements-in-a-transaction.md)
|
||||
@@ -734,7 +785,9 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
|
||||
- [Allow Neovim To Copy/Paste With System Clipboard](neovim/allow-neovim-to-copy-paste-with-system-clipboard.md)
|
||||
- [Create User Command To Open Init Config](neovim/create-user-command-to-open-init-config.md)
|
||||
- [Jump Between Changes In Current File](neovim/jump-between-changes-in-current-file.md)
|
||||
- [Run A Lua Statement From The Command Prompt](neovim/run-a-lua-statement-from-the-command-prompt.md)
|
||||
- [Run nvim With Factory Defaults](neovim/run-nvim-with-factory-defaults.md)
|
||||
- [Set Up Vim-Plug With Neovim](neovim/set-up-vim-plug-with-neovim.md)
|
||||
|
||||
### Netlify
|
||||
@@ -772,6 +825,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
|
||||
### Planetscale
|
||||
|
||||
- [See What Databases You Have Access To](planetscale/see-what-databases-you-have-access-to.md)
|
||||
- [Seed Production Data Into Another Branch](planetscale/seed-production-data-into-another-branch.md)
|
||||
|
||||
### pnpm
|
||||
@@ -798,6 +852,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Check If The Local Server Is Running](postgres/check-if-the-local-server-is-running.md)
|
||||
- [Check If User Role Exists For Database](postgres/check-if-user-role-exists-for-database.md)
|
||||
- [Check Table For Any Oprhaned Records](postgres/check-table-for-any-orphaned-records.md)
|
||||
- [Check The Size Of Databases In A Cluster](postgres/check-the-size-of-databases-in-a-cluster.md)
|
||||
- [Checking Inequality](postgres/checking-inequality.md)
|
||||
- [Checking The Type Of A Value](postgres/checking-the-type-of-a-value.md)
|
||||
- [Clear The Screen In psql](postgres/clear-the-screen-in-psql.md)
|
||||
@@ -818,6 +873,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Create A Table From The Structure Of Another](postgres/create-a-table-from-the-structure-of-another.md)
|
||||
- [Create An Index Across Two Columns](postgres/create-an-index-across-two-columns.md)
|
||||
- [Create An Index Without Locking The Table](postgres/create-an-index-without-locking-the-table.md)
|
||||
- [Create And Execute SQL Statements With \gexec](postgres/create-and-execute-sql-statements-with-gexec.md)
|
||||
- [Create Database Uses Template1](postgres/create-database-uses-template1.md)
|
||||
- [Create hstore From Two Arrays](postgres/create-hstore-from-two-arrays.md)
|
||||
- [Create Table Adds A Data Type](postgres/create-table-adds-a-data-type.md)
|
||||
@@ -901,6 +957,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Prevent A Query From Running Too Long](postgres/prevent-a-query-from-running-too-long.md)
|
||||
- [Print The Query Buffer In psql](postgres/print-the-query-buffer-in-psql.md)
|
||||
- [Put Unique Constraint On Generated Column](postgres/put-unique-constraint-on-generated-column.md)
|
||||
- [References Target Primary Key By Default](postgres/references-target-primary-key-by-default.md)
|
||||
- [Remove Not Null Constraint From A Column](postgres/remove-not-null-constraint-from-a-column.md)
|
||||
- [Renaming A Sequence](postgres/renaming-a-sequence.md)
|
||||
- [Renaming A Table](postgres/renaming-a-table.md)
|
||||
@@ -911,6 +968,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Set Inclusion With hstore](postgres/set-inclusion-with-hstore.md)
|
||||
- [Set A Seed For The Random Number Generator](postgres/set-a-seed-for-the-random-number-generator.md)
|
||||
- [Set A Statement Timeout Threshold For A Session](postgres/set-a-statement-timeout-threshold-for-a-session.md)
|
||||
- [Set Up A Project-Local Cluster With Postgres.app](postgres/set-up-a-project-local-cluster-with-postgres-app.md)
|
||||
- [Sets With The Values Command](postgres/sets-with-the-values-command.md)
|
||||
- [Shorthand Absolute Value Operator](postgres/shorthand-absolute-value-operator.md)
|
||||
- [Show All Versions Of An Operator](postgres/show-all-versions-of-an-operator.md)
|
||||
@@ -1016,6 +1074,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Comparing DateTimes Down To Second Precision](rails/comparing-datetimes-down-to-second-precision.md)
|
||||
- [Conditional Class Selectors in Haml](rails/conditional-class-selectors-in-haml.md)
|
||||
- [Convert A Symbol To A Constant](rails/convert-a-symbol-to-a-constant.md)
|
||||
- [Convert JSON Field To Hash With Indifferent Access](rails/convert-json-field-to-hash-with-indifferent-access.md)
|
||||
- [Count The Number Of Records By Attribute](rails/count-the-number-of-records-by-attribute.md)
|
||||
- [Create A Custom Named References Column](rails/create-a-custom-named-references-column.md)
|
||||
- [Create A Join Table With The Migration DSL](rails/create-a-join-table-with-the-migration-dsl.md)
|
||||
@@ -1023,6 +1082,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Creating Records of Has_One Associations](rails/creating-records-of-has-one-associations.md)
|
||||
- [Custom Validation Message](rails/custom-validation-message.md)
|
||||
- [Customize Paths And Helpers For Devise Routes](rails/customize-paths-and-helpers-for-devise-routes.md)
|
||||
- [Customize Template For New Schema Migration](rails/customize-template-for-new-schema-migration.md)
|
||||
- [Customize The Path Of A Resource Route](rails/customize-the-path-of-a-resource-route.md)
|
||||
- [Define The Root Path For The App](rails/define-the-root-path-for-the-app.md)
|
||||
- [Delete Paranoid Records](rails/delete-paranoid-records.md)
|
||||
@@ -1035,10 +1095,13 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Ensure A Rake Task Cannot Write Data](rails/ensure-a-rake-task-cannot-write-data.md)
|
||||
- [Ensure Migrations Use The Latest Schema](rails/ensure-migrations-use-the-latest-schema.md)
|
||||
- [Ensure Record Saved With after_commit Callback](rails/ensure-record-saved-with-after-commit-callback.md)
|
||||
- [Filter ActiveModel Validation Errors](rails/filter-active-model-validation-errors.md)
|
||||
- [Filter ActiveStorage Blobs To Only Images](rails/filter-active-storage-blobs-to-only-images.md)
|
||||
- [Find Or Create A Record With FactoryBot](rails/find-or-create-a-record-with-factory-bot.md)
|
||||
- [Find Records With Multiple Associated Records](rails/find-records-with-multiple-associated-records.md)
|
||||
- [Force All Users To Sign Out](rails/force-all-users-to-sign-out.md)
|
||||
- [Format DateTime With Builtin Formats](rails/format-datetime-with-builtin-formats.md)
|
||||
- [Format Specific html.erb Template Files](rails/format-specific-html-erb-template-files.md)
|
||||
- [Generate A Model](rails/generate-a-model.md)
|
||||
- [Generate A Rails App From The Main Branch](rails/generate-a-rails-app-from-the-main-branch.md)
|
||||
- [Generating And Executing SQL](rails/generating-and-executing-sql.md)
|
||||
@@ -1068,6 +1131,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Make A String Attribute Easy To Inquire About](rails/make-a-string-attribute-easy-to-inquire-about.md)
|
||||
- [Make ActionMailer Synchronous In Test](rails/make-action-mailer-synchronous-in-test.md)
|
||||
- [Make Remove Column Migration Reversible](rails/make-remove-column-migration-reversible.md)
|
||||
- [Manage Timestamps With Upsert](rails/manage-timestamps-with-upsert.md)
|
||||
- [Manually Run A Migration From Rails Console](rails/manually-run-a-migration-from-rails-console.md)
|
||||
- [Mark For Destruction](rails/mark-for-destruction.md)
|
||||
- [Mask An ActiveRecord Attribute](rails/mask-an-activerecord-attribute.md)
|
||||
@@ -1076,6 +1140,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Mock Rails Environment With An Inquiry Instance](rails/mock-rails-environment-with-an-inquiry-instance.md)
|
||||
- [Order Matters For `rescue_from` Blocks](rails/order-matters-for-rescue-from-blocks.md)
|
||||
- [Override Text Displayed By Form Label](rails/override-text-displayed-by-form-label.md)
|
||||
- [Parameterize A String With Underscores](rails/parameterize-a-string-with-underscores.md)
|
||||
- [Params Includes Submission Button Info](rails/params-includes-submission-button-info.md)
|
||||
- [Params Is A Hash With Indifferent Access](rails/params-is-a-hash-with-indifferent-access.md)
|
||||
- [Parse Query Params From A URL](rails/parse-query-params-from-a-url.md)
|
||||
@@ -1084,7 +1149,9 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Polymorphic Path Helpers](rails/polymorphic-path-helpers.md)
|
||||
- [Prefer select_all Over execute For Read Queries](rails/prefer-select-all-over-execute-for-read-queries.md)
|
||||
- [Pretend Generations](rails/pretend-generations.md)
|
||||
- [Prevent Mailer Previews From Cluttering Database](rails/prevent-mailer-previews-from-cluttering-database.md)
|
||||
- [Prevent Writes With A Sandboxed Rails Console](rails/prevent-writes-with-a-sandboxed-rails-console.md)
|
||||
- [Provide Fake Form Helper To Controllers](rails/provide-fake-form-helper-to-controllers.md)
|
||||
- [Query A Single Value From The Database](rails/query-a-single-value-from-the-database.md)
|
||||
- [Read In Environment-Specific Config Values](rails/read-in-environment-specific-config-values.md)
|
||||
- [Read-Only Models](rails/read-only-models.md)
|
||||
@@ -1104,9 +1171,12 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Rounding Numbers With Precision](rails/rounding-numbers-with-precision.md)
|
||||
- [Run A Rake Task Programmatically](rails/run-a-rake-task-programmatically.md)
|
||||
- [Run Commands With Specific Rails Version](rails/run-commands-with-specific-rails-version.md)
|
||||
- [Run Dev Processes With Overmind Instead Of Foreman](rails/run-dev-processes-with-overmind-instead-of-foreman.md)
|
||||
- [Run Rails Console With Remote Dokku App](rails/run-rails-console-with-remote-dokku-app.md)
|
||||
- [Run Some Code Whenever Rails Console Starts](rails/run-some-code-whenever-rails-console-starts.md)
|
||||
- [Scaffold Auth Functionality With Rails 8 Generator](rails/scaffold-auth-functionality-with-rails-8-generator.md)
|
||||
- [Schedule Sidekiq Jobs Out Into The Future](rails/schedule-sidekiq-jobs-out-into-the-future.md)
|
||||
- [Scope Records To A Lower Or Upper Bound](rails/scope-records-to-a-lower-or-upper-bound.md)
|
||||
- [Secure Passwords With Rails And Bcrypt](rails/secure-passwords-with-rails-and-bcrypt.md)
|
||||
- [Select A Select By Selector](rails/select-a-select-by-selector.md)
|
||||
- [Select A Specific Rails Version To Install](rails/select-a-specific-rails-version-to-install.md)
|
||||
@@ -1135,6 +1205,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Update Column Versus Update Attribute](rails/update-column-versus-update-attribute.md)
|
||||
- [Upgrading Your Manifest For Sprocket's 4](rails/upgrading-your-manifest-for-sprockets-4.md)
|
||||
- [Use IRB And Ruby Flags With Rails Console](rails/use-irb-and-ruby-flags-with-rails-console.md)
|
||||
- [Use .ruby Extension For Template File](rails/use-ruby-extension-for-template-file.md)
|
||||
- [Useful ActiveSupport Constants For Durations](rails/useful-active-support-constants-for-durations.md)
|
||||
- [Validate Column Data With Check Constraints](rails/validate-column-data-with-check-constraints.md)
|
||||
- [Verify And Read A Signed Cookie Value](rails/verify-and-read-a-signed-cookie-value.md)
|
||||
@@ -1274,8 +1345,9 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Add Progress Reporting To Long-Running Script](ruby/add-progress-reporting-to-long-running-script.md)
|
||||
- [Are They All True?](ruby/are-they-all-true.md)
|
||||
- [Assert About An Object's Attributes With RSpec](ruby/assert-about-an-objects-attributes-with-rspec.md)
|
||||
- [Audit Your Ruby Project For Any CVEs](ruby/audit-your-ruby-project-for-any-cves.md)
|
||||
- [Assoc For Hashes](ruby/assoc-for-hashes.md)
|
||||
- [Audit Your Ruby Project For Any CVEs](ruby/audit-your-ruby-project-for-any-cves.md)
|
||||
- [Avoid Double Negation With Minitest Refute](ruby/avoid-double-negation-with-minitest-refute.md)
|
||||
- [Block Comments](ruby/block-comments.md)
|
||||
- [Block Syntaxes Have Different Precedence](ruby/block-syntaxes-have-different-precedence.md)
|
||||
- [Build HTTP And HTTPS URLs](ruby/build-http-and-https-urls.md)
|
||||
@@ -1295,8 +1367,11 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Create a CSV::Table Object](ruby/create-a-csv-table-object.md)
|
||||
- [Create A Hash From An Array Of Arrays](ruby/create-a-hash-from-an-array-of-arrays.md)
|
||||
- [Create Listing Of All Middleman Pages](ruby/create-listing-of-all-middleman-pages.md)
|
||||
- [Create Mock Class That Can Be Overridden](ruby/create-mock-class-that-can-be-overridden.md)
|
||||
- [Create A Module Of Utility Functions](ruby/create-a-module-of-utility-functions.md)
|
||||
- [Create Named Structs With Struct.new](ruby/create-named-structs-with-struct-new.md)
|
||||
- [Create Thumbnail Image For A PDF](ruby/create-thumbnail-image-for-a-pdf.md)
|
||||
- [Decompose Unicode Character With Diacritic Mark](ruby/decompose-unicode-character-with-diacritic-mark.md)
|
||||
- [Defaulting To Frozen String Literals](ruby/defaulting-to-frozen-string-literals.md)
|
||||
- [Define A Custom RSpec Matcher](ruby/define-a-custom-rspec-matcher.md)
|
||||
- [Define A Method On A Struct](ruby/define-a-method-on-a-struct.md)
|
||||
@@ -1328,6 +1403,8 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Generate A Signed JWT Token](ruby/generate-a-signed-jwt-token.md)
|
||||
- [Generate Ruby Version And Gemset Files With RVM](ruby/generate-ruby-version-and-gemset-files-with-rvm.md)
|
||||
- [Get Info About Your RubyGems Environment](ruby/get-info-about-your-ruby-gems-environment.md)
|
||||
- [Get Specific Values From Arrays And Hashes](ruby/get-specific-values-from-hashes-and-arrays.md)
|
||||
- [Get The Names Of The Month](ruby/get-the-names-of-the-month.md)
|
||||
- [Get The Output Of Running A System Program](ruby/get-the-output-of-running-a-system-program.md)
|
||||
- [Get UTC Offset For Different Time Zones](ruby/get-utc-offset-for-different-time-zones.md)
|
||||
- [Identify Outdated Gems](ruby/identify-outdated-gems.md)
|
||||
@@ -1335,15 +1412,18 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Iterate With An Offset Index](ruby/iterate-with-an-offset-index.md)
|
||||
- [Include Extra Context In A Honeybadger Notify](ruby/include-extra-context-in-a-honeybadger-notify.md)
|
||||
- [Ins And Outs Of Pry](ruby/ins-and-outs-of-pry.md)
|
||||
- [Install And Require Gems Inline Without Gemfile](ruby/install-and-require-gems-inline-without-gemfile.md)
|
||||
- [Install Latest Version Of Ruby With asdf](ruby/install-latest-version-of-ruby-with-asdf.md)
|
||||
- [Invoking Rake Tasks Multiple Times](ruby/invoking-rake-tasks-multiple-times.md)
|
||||
- [IRB Has Built-In Benchmarking With Ruby 3](ruby/irb-has-built-in-benchmarking-with-ruby-3.md)
|
||||
- [Join URI Path Parts](ruby/join-uri-path-parts.md)
|
||||
- [Jump Out Of A Nested Context With Throw/Catch](ruby/jump-out-of-a-nested-context-with-throw-catch.md)
|
||||
- [Last Raised Exception In The Call Stack](ruby/last-raised-exception-in-the-call-stack.md)
|
||||
- [Limit Split](ruby/limit-split.md)
|
||||
- [List The Running Ruby Version](ruby/list-the-running-ruby-version.md)
|
||||
- [Listing Local Variables](ruby/listing-local-variables.md)
|
||||
- [Make An Executable Ruby Script](ruby/make-an-executable-ruby-script.md)
|
||||
- [Make Structs Easier To Use With Keyword Initialization](ruby/make-structs-easier-to-use-with-keyword-initialization.md)
|
||||
- [Map With Index Over An Array](ruby/map-with-index-over-an-array.md)
|
||||
- [Mock Method Chain Calls With RSpec](ruby/mock-method-chain-calls-with-rspec.md)
|
||||
- [Mocking Requests With Partial URIs Using Regex](ruby/mocking-requests-with-partial-uris-using-regex.md)
|
||||
@@ -1370,6 +1450,8 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Rake Only Lists Tasks With Descriptions](ruby/rake-only-lists-tasks-with-descriptions.md)
|
||||
- [Read The First Line From A File](ruby/read-the-first-line-from-a-file.md)
|
||||
- [Refer To Implicit Block Argument With It](ruby/refer-to-implicit-block-argument-with-it.md)
|
||||
- [Reference Hash Key With Safe Navigation](ruby/reference-hash-key-with-safe-navigation.md)
|
||||
- [Regenerate Lock File With Newer Bundler](ruby/regenerate-lock-file-with-newer-bundler.md)
|
||||
- [Rendering ERB](ruby/rendering-erb.md)
|
||||
- [Replace The Current Process With An External Command](ruby/replace-the-current-process-with-an-external-command.md)
|
||||
- [Require Entire Gemfile In Pry Session](ruby/require-entire-gemfile-in-pry-session.md)
|
||||
@@ -1384,6 +1466,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Scripting With RVM](ruby/scripting-with-rvm.md)
|
||||
- [Scroll To Top Of Page With Capybara](ruby/scroll-to-top-of-page-with-capybara.md)
|
||||
- [Search For Gem Versions Available To Install](ruby/search-for-gem-versions-available-to-install.md)
|
||||
- [Set Default Tasks For Rake To Run](ruby/set-default-tasks-for-rake-to-run.md)
|
||||
- [Set RVM Default Ruby](ruby/set-rvm-default-ruby.md)
|
||||
- [Shift The Month On A Date Object](ruby/shift-the-month-on-a-date-object.md)
|
||||
- [Show Public Methods With Pry](ruby/show-public-methods-with-pry.md)
|
||||
@@ -1428,6 +1511,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [OSX sed Does Regex A Bit Different](sed/osx-sed-does-regex-a-bit-different.md)
|
||||
- [Output Only Lines Involved In A Substitution](sed/output-only-lines-involved-in-a-substitution.md)
|
||||
- [Reference A Capture In The Regex](sed/reference-a-capture-in-the-regex.md)
|
||||
- [Reference The Full Match In The Replacement](sed/reference-the-full-match-in-the-replacement.md)
|
||||
- [Use An Alternative Delimiter In A Substitution](sed/use-an-alternative-delimiter-in-a-substitution.md)
|
||||
|
||||
### Shell
|
||||
@@ -1448,12 +1532,19 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
|
||||
- [Apply Tailwind Classes To Existing CSS Class](tailwind/apply-tailwind-classes-to-existing-css-class.md)
|
||||
- [Base Styles For Text Link](tailwind/base-styles-for-text-link.md)
|
||||
- [Disable And Enable A Button](tailwind/disable-and-enable-a-button.md)
|
||||
- [Specify Paths For Purging Unused CSS](tailwind/specify-paths-for-purging-unused-css.md)
|
||||
- [Use Tailwind Typography Prose In Dark Mode](tailwind/use-tailwind-typography-prose-in-dark-mode.md)
|
||||
|
||||
### Taskfile
|
||||
|
||||
- [Create Interactive Picker For Set Of Subtasks](taskfile/create-interactive-picker-for-set-of-subtasks.md)
|
||||
- [Run A Task If It Meets Criteria](taskfile/run-a-task-if-it-meets-criteria.md)
|
||||
|
||||
### tmux
|
||||
|
||||
- [Access Past Copy Buffer History](tmux/access-past-copy-buffer-history.md)
|
||||
- [Add Bindings To Split Panes To Current Directory](tmux/add-bindings-to-split-panes-to-current-directory.md)
|
||||
- [Adjusting Window Pane Size](tmux/adjusting-window-pane-size.md)
|
||||
- [Break Current Pane Out To Separate Window](tmux/break-current-pane-out-to-separate-window.md)
|
||||
- [Change Base Directory Of Existing Session](tmux/change-base-directory-of-existing-session.md)
|
||||
@@ -1483,6 +1574,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Reset An Option Back To Its Default Value](tmux/reset-an-option-back-to-its-default-value.md)
|
||||
- [Set Environment Variables When Creating Session](tmux/set-environment-variables-when-creating-session.md)
|
||||
- [Set Session Specific Environment Variables](tmux/set-session-specific-environment-variables.md)
|
||||
- [Set Up Forwarding Prefix For Nested Session](tmux/set-up-forwarding-prefix-for-nested-session.md)
|
||||
- [Show The Current Value For An Option](tmux/show-the-current-value-for-an-option.md)
|
||||
- [Swap Split Panes](tmux/swap-split-panes.md)
|
||||
- [Switch To A Specific Session And Window](tmux/switch-to-a-specific-session-and-window.md)
|
||||
@@ -1516,6 +1608,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
### Unix
|
||||
|
||||
- [All The Environment Variables](unix/all-the-environment-variables.md)
|
||||
- [Authorize A cURL Request](unix/authorize-a-curl-request.md)
|
||||
- [Cat A File With Line Numbers](unix/cat-a-file-with-line-numbers.md)
|
||||
- [Cat Files With Color Using Bat](unix/cat-files-with-color-using-bat.md)
|
||||
- [Change Default Shell For A User](unix/change-default-shell-for-a-user.md)
|
||||
@@ -1539,13 +1632,16 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Count The Number Of ripgrep Pattern Matches](unix/count-the-number-of-ripgrep-pattern-matches.md)
|
||||
- [Count The Number Of Words On A Webpage](unix/count-the-number-of-words-on-a-webpage.md)
|
||||
- [Create A File Descriptor with Process Substitution](unix/create-a-file-descriptor-with-process-substitution.md)
|
||||
- [Create A Filename With The Current Date](unix/create-a-filename-with-the-current-date.md)
|
||||
- [Create A Sequence Of Values With A Step](unix/create-a-sequence-of-values-with-a-step.md)
|
||||
- [Curl With Cookies](unix/curl-with-cookies.md)
|
||||
- [Curling For Headers](unix/curling-for-headers.md)
|
||||
- [Curling With Basic Auth Credentials](unix/curling-with-basic-auth-credentials.md)
|
||||
- [Determine ipv4 And ipv6 Public IP Addresses](unix/determine-ipv4-and-ipv6-public-ip-addresses.md)
|
||||
- [Different Ways To Generate A v4 UUID](unix/different-ways-to-generate-a-v4-uuid.md)
|
||||
- [Display All The Terminal Colors](unix/display-all-the-terminal-colors.md)
|
||||
- [Display Free Disk Space](unix/display-free-disk-space.md)
|
||||
- [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)
|
||||
@@ -1553,6 +1649,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Enable Multi-Select Of Results With fzf](unix/enable-multi-select-of-results-with-fzf.md)
|
||||
- [Exclude A Command From The ZSH History File](unix/exclude-a-command-from-the-zsh-history-file.md)
|
||||
- [Exclude A Directory With Find](unix/exclude-a-directory-with-find.md)
|
||||
- [Exclude A Specific File From fd Results](unix/exclude-a-specific-file-from-fd-results.md)
|
||||
- [Exclude Certain Files From An rsync Run](unix/exclude-certain-files-from-an-rsync-run.md)
|
||||
- [Figure Out The Week Of The Year From The Terminal](unix/figure-out-the-week-of-the-year-from-the-terminal.md)
|
||||
- [File Type Info With File](unix/file-type-info-with-file.md)
|
||||
@@ -1572,6 +1669,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Fix Unlinked Node Binaries With asdf](unix/fix-unlinked-node-binaries-with-asdf.md)
|
||||
- [Forward Multiple Ports Over SSH](unix/forward-multiple-ports-over-ssh.md)
|
||||
- [Generate A SAML Key And Certificate Pair](unix/generate-a-saml-key-and-certificate-pair.md)
|
||||
- [Generate A Sequence Of Numbered Items](unix/generate-a-sequence-of-numbered-items.md)
|
||||
- [Generate Base64 Encoding Without Newlines](unix/generate-base64-encoding-without-newlines.md)
|
||||
- [Generate Random 20-Character Hex String](unix/generate-random-20-character-hex-string.md)
|
||||
- [Get A List Of Locales On Your System](unix/get-a-list-of-locales-on-your-system.md)
|
||||
@@ -1586,12 +1684,14 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Grep For Files Without A Match](unix/grep-for-files-without-a-match.md)
|
||||
- [Grep For Files With Multiple Matches](unix/grep-for-files-with-multiple-matches.md)
|
||||
- [Grep For Multiple Patterns](unix/grep-for-multiple-patterns.md)
|
||||
- [Have Script ShellCheck Itself When Executing](unix/have-script-shellcheck-itself-when-executing.md)
|
||||
- [Hexdump A Compiled File](unix/hexdump-a-compiled-file.md)
|
||||
- [Ignore A Directory During ripgrep Search](unix/ignore-a-directory-during-ripgrep-search.md)
|
||||
- [Ignore The Alias When Running A Command](unix/ignore-the-alias-when-running-a-command.md)
|
||||
- [Include Ignore Files In Ripgrep Search](unix/include-ignore-files-in-ripgrep-search.md)
|
||||
- [Interactively Browse Available Node Versions](unix/interactively-browse-availabile-node-versions.md)
|
||||
- [Interactively Switch asdf Package Versions](unix/interactively-switch-asdf-package-versions.md)
|
||||
- [Interpret Cron Schedule From The CLI](unix/interpret-cron-schedule-from-the-cli.md)
|
||||
- [Jump To The Ends Of Your Shell History](unix/jump-to-the-ends-of-your-shell-history.md)
|
||||
- [Kill Everything Running On A Certain Port](unix/kill-everything-running-on-a-certain-port.md)
|
||||
- [Killing A Frozen SSH Session](unix/killing-a-frozen-ssh-session.md)
|
||||
@@ -1614,6 +1714,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Load Env Vars In Bash Script](unix/load-env-vars-in-bash-script.md)
|
||||
- [Look Through All Files That Have Been Git Stashed](unix/look-through-all-files-that-have-been-git-stashed.md)
|
||||
- [Make Direnv Less Noisy](unix/make-direnv-less-noisy.md)
|
||||
- [Make Neovim The Default Way To View Man Pages](unix/make-neovim-the-default-way-to-view-man-pages.md)
|
||||
- [Manually Pass Two Git Files To Delta](unix/manually-pass-two-git-files-to-delta.md)
|
||||
- [Map A Domain To localhost](unix/map-a-domain-to-localhost.md)
|
||||
- [Negative Look-Ahead Search With ripgrep](unix/negative-look-ahead-search-with-ripgrep.md)
|
||||
@@ -1631,6 +1732,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Produce A Lowercase V4 UUID](unix/produce-a-lowercase-v4-uuid.md)
|
||||
- [Provide A Fallback Value For Unset Parameter](unix/provide-a-fallback-value-for-unset-parameter.md)
|
||||
- [Remove A Directory Called `-p`](unix/remove-a-directory-called-dash-p.md)
|
||||
- [Rename A Bunch Of Files By Constructing mv Commands](unix/rename-a-bunch-of-files-by-constructing-mv-commands.md)
|
||||
- [Repeat Yourself](unix/repeat-yourself.md)
|
||||
- [Replace Pattern Across Many Files In A Project](unix/replace-pattern-across-many-files-in-a-project.md)
|
||||
- [Run A Command Repeatedly Several Times](unix/run-a-command-repeatedly-several-times.md)
|
||||
@@ -1644,6 +1746,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Securely Remove Files](unix/securely-remove-files.md)
|
||||
- [See Where asdf Gets Current Tool Version](unix/see-where-asdf-gets-current-tool-version.md)
|
||||
- [Set The asdf Package Version For A Single Shell](unix/set-the-asdf-package-version-for-a-single-shell.md)
|
||||
- [Shorten SSH Commands With Aliases](unix/shorten-ssh-commands-with-aliases.md)
|
||||
- [Show A File Preview When Searching With FZF](unix/show-a-file-preview-when-searching-with-fzf.md)
|
||||
- [Show Disk Usage For The Current Directory](unix/show-disk-usage-for-the-current-directory.md)
|
||||
- [Show The Size Of Everything In A Directory](unix/show-the-size-of-everything-in-a-directory.md)
|
||||
@@ -1655,6 +1758,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Switch Versions of a Brew Formula](unix/switch-versions-of-a-brew-formula.md)
|
||||
- [Tell direnv To Load The Env File](unix/tell-direnv-to-load-the-env-file.md)
|
||||
- [Touch Access And Modify Times Individually](unix/touch-access-and-modify-times-individually.md)
|
||||
- [Transform Text To Lowercase](unix/transform-text-to-lowercase.md)
|
||||
- [Type Fewer Paths With Brace Expansion](unix/type-fewer-paths-with-brace-expansion.md)
|
||||
- [Undo Changes Made To Current Terminal Prompt](unix/undo-changes-made-to-current-terminal-prompt.md)
|
||||
- [Undo Some Command Line Editing](unix/undo-some-command-line-editing.md)
|
||||
@@ -1696,6 +1800,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Breaking The Undo Sequence](vim/breaking-the-undo-sequence.md)
|
||||
- [Buffer Time Travel](vim/buffer-time-travel.md)
|
||||
- [Build And Install A Go Program](vim/build-and-install-a-go-program.md)
|
||||
- [Bypass On-Save Tooling When Writing File](vim/bypass-on-save-tooling-when-writing-file.md)
|
||||
- [Case-Aware Substitution With vim-abolish](vim/case-aware-substitution-with-vim-abolish.md)
|
||||
- [Case-Insensitive Substitution](vim/case-insensitive-substitution.md)
|
||||
- [Center The Cursor](vim/center-the-cursor.md)
|
||||
@@ -1798,6 +1903,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Replace A Character](vim/replace-a-character.md)
|
||||
- [Reset Target tslime Pane](vim/reset-target-tslime-pane.md)
|
||||
- [Reverse A Group Of Lines](vim/reverse-a-group-of-lines.md)
|
||||
- [Reword A Commit Message With Fugitive](vim/reword-a-commit-message-with-fugitive.md)
|
||||
- [Rotate Everything By 13 Letters](vim/rotate-everything-by-13-letters.md)
|
||||
- [Rotate The Orientation Of Split Windows](vim/rotate-the-orientation-of-split-windows.md)
|
||||
- [Running Bundle With vim-bundler](vim/running-bundle-with-vim-bundler.md)
|
||||
@@ -1846,6 +1952,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Find The Location Of User Settings JSON File](vscode/find-the-location-of-user-settings-json-file.md)
|
||||
- [Jump To Problems In The Current File](vscode/jump-to-problems-in-the-current-file.md)
|
||||
- [Open An Integrated Terminal Window](vscode/open-an-integrated-terminal-window.md)
|
||||
- [Open File On Remote Like GitHub](vscode/open-file-on-remote-like-github.md)
|
||||
- [Pop Open The Quick Fix Window](vscode/pop-open-the-quick-fix-window.md)
|
||||
- [Step Through Project-Wide Search Results](vscode/step-through-project-wide-search-results.md)
|
||||
- [Synchronize Vim Clipboard With System Clipboard](vscode/synchronize-vim-clipboard-with-system-clipboard.md)
|
||||
@@ -1875,6 +1982,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Create A Local Sanity Dataset Backup](workflow/create-a-local-sanity-dataset-backup.md)
|
||||
- [Create A Public URL For A Local Server](workflow/create-a-public-url-for-a-local-server.md)
|
||||
- [Create Todo Items In Logseq](workflow/create-todo-items-in-logseq.md)
|
||||
- [Do Project Time Tracking From The CLI](workflow/do-project-time-tracking-from-the-cli.md)
|
||||
- [Enable Dev Tools For Safari](workflow/enable-dev-tools-for-safari.md)
|
||||
- [Forward Stripe Events To Local Server](workflow/forward-stripe-events-to-local-server.md)
|
||||
- [Get URL For GitHub User Profile Photo](workflow/get-url-for-github-user-profile-photo.md)
|
||||
@@ -1893,6 +2001,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Temporarily Hide CleanShot X Capture Previews](workflow/temporarily-hide-cleanshot-x-capture-previews.md)
|
||||
- [Toggle Between Stories In Storybook](workflow/toggle-between-stories-in-storybook.md)
|
||||
- [Update asdf Plugins With Latest Package Versions](workflow/update-asdf-plugins-with-latest-package-versions.md)
|
||||
- [View A Nicely-Formatted CSV In Terminal](workflow/view-a-nicely-formatted-csv-in-terminal.md)
|
||||
- [View The PR For The Current GitHub Branch](workflow/view-the-pr-for-the-current-github-branch.md)
|
||||
|
||||
### XState
|
||||
@@ -1937,6 +2046,10 @@ current number of TILs and display the result in the command tray.
|
||||
|
||||
## About
|
||||
|
||||
I've written more about how this repo came to be in [How I Built a Learning
|
||||
Machine](https://dev.to/jbranchaud/how-i-built-a-learning-machine-45k9) and [A
|
||||
Decade of TILs](https://www.visualmode.dev/a-decade-of-tils).
|
||||
|
||||
I shamelessly stole this idea from
|
||||
[thoughtbot/til](https://github.com/thoughtbot/til).
|
||||
|
||||
@@ -1948,7 +2061,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.
|
||||
|
||||
78
Taskfile.yml
Normal file
78
Taskfile.yml
Normal file
@@ -0,0 +1,78 @@
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
NOTES_DIR: notes
|
||||
NOTES_FILE: '{{.NOTES_DIR}}/NOTES.md'
|
||||
EDITOR: '{{.EDITOR | default "nvim"}}'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
desc: Show available commands
|
||||
cmds:
|
||||
- task --list
|
||||
|
||||
notes:
|
||||
desc: Interactive picker for notes tasks
|
||||
cmds:
|
||||
- |
|
||||
TASK=$(task --list | grep "^\* notes:" | sed 's/^\* notes://' | sed 's/\s\+/ - /' | fzf --prompt="Select notes task: " --height=40% --reverse) || true
|
||||
if [ -n "$TASK" ]; then
|
||||
TASK_NAME=$(echo "$TASK" | awk '{print $1}' | sed 's/:$//')
|
||||
task notes:$TASK_NAME
|
||||
fi
|
||||
interactive: true
|
||||
silent: true
|
||||
|
||||
notes:edit:
|
||||
desc: All-in-one edit, commit, and push notes
|
||||
cmds:
|
||||
- task notes:open
|
||||
- task notes:push
|
||||
|
||||
notes:sync:
|
||||
desc: Sync latest changes from the notes submodule
|
||||
cmds:
|
||||
- cd {{.NOTES_DIR}} && git checkout main && git pull
|
||||
silent: false
|
||||
|
||||
notes:open:
|
||||
desc: Opens NOTES.md (syncs latest changes first) in default editor
|
||||
deps: [notes:sync]
|
||||
cmds:
|
||||
- $EDITOR {{.NOTES_FILE}}
|
||||
interactive: true
|
||||
|
||||
notes:push:
|
||||
desc: Commit and push changes to notes submodule
|
||||
dir: '{{.NOTES_DIR}}'
|
||||
cmds:
|
||||
- git add NOTES.md
|
||||
- git commit -m "Update notes - $(date '+%Y-%m-%d %H:%M')"
|
||||
- git pull --rebase
|
||||
- git push
|
||||
status:
|
||||
- git diff --exit-code NOTES.md
|
||||
silent: false
|
||||
|
||||
notes:status:
|
||||
desc: Check status of notes submodule
|
||||
dir: '{{.NOTES_DIR}}'
|
||||
cmds:
|
||||
- git status
|
||||
|
||||
notes:pull:
|
||||
desc: Pull latest changes (alias for sync)
|
||||
cmds:
|
||||
- task notes:sync
|
||||
|
||||
notes:diff:
|
||||
desc: Show uncommitted changes in notes
|
||||
dir: '{{.NOTES_DIR}}'
|
||||
cmds:
|
||||
- git diff NOTES.md
|
||||
|
||||
notes:log:
|
||||
desc: Show recent commit history for notes
|
||||
dir: '{{.NOTES_DIR}}'
|
||||
cmds:
|
||||
- git log --oneline -10
|
||||
29
aws/list-rds-snapshots-with-matching-identifier-prefix.md
Normal file
29
aws/list-rds-snapshots-with-matching-identifier-prefix.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# List RDS Snapshots With Matching Identifier Prefix
|
||||
|
||||
I'm working on a script that manually creates a snapshot which it will then
|
||||
restore to a temporary database that I can scrub and dump. The snapshots that
|
||||
this script takes are _manual_ and they are named with identifiers that have a
|
||||
defining prefix (`dev-snapshot-`). Besides the few snapshots created by this
|
||||
script, there are tons of automated snapshots that RDS creates for
|
||||
backup/recovery purposes.
|
||||
|
||||
I want to list any snapshots that have been created by the script. I can do
|
||||
this with the `describe-db-snapshots` command and some filters.
|
||||
|
||||
```bash
|
||||
$ aws rds describe-db-snapshots \
|
||||
--snapshot-type manual \
|
||||
--query "DBSnapshots[?starts_with(DBSnapshotIdentifier, 'dev-snapshot-')].DBSnapshotIdentifier" \
|
||||
--no-cli-pager
|
||||
|
||||
[
|
||||
"dev-snapshot-20250327-155355"
|
||||
]
|
||||
```
|
||||
|
||||
There are two key pieces. The `--snapshot-type manual` filter excludes all
|
||||
those automated snapshots. The `--query` both filters to any snapshots whose
|
||||
identifier `?starts_with` the prefix `dev-snapshot-` and then refines the
|
||||
output to just the `DBSnapshotIdentifier` instead of the entire JSON object.
|
||||
|
||||
[source](https://docs.aws.amazon.com/cli/latest/reference/rds/describe-db-snapshots.html)
|
||||
40
brew/clean-up-your-brew-installations.md
Normal file
40
brew/clean-up-your-brew-installations.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Clean Up Your Brew Installations
|
||||
|
||||
Over time as you upgrade brew-installed programs and make changes to your
|
||||
`Brewfile`, your machine will have artifacts left behind that you no longer
|
||||
need.
|
||||
|
||||
Periodically, it is good to clean things up.
|
||||
|
||||
First, you can get a summary of stale and outdated files that brew has
|
||||
installed. Use the `--dry-run` flag.
|
||||
|
||||
```bash
|
||||
$ brew cleanup --dry-run
|
||||
```
|
||||
|
||||
If you feel good about what you see in the output, then give things a clean.
|
||||
|
||||
```bash
|
||||
$ brew cleanup
|
||||
```
|
||||
|
||||
Second, if you are using a `Brewfile` to manage what `brew` installs, then you
|
||||
can instruct `brew` to uninstall any dependencies that aren't specified in that
|
||||
file.
|
||||
|
||||
By default it operates as a dry run and the `--force` flag will be needed to
|
||||
actually do the cleanup. And specify the filename if it doesn't match the
|
||||
default of `Brewfile`.
|
||||
|
||||
```bash
|
||||
$ brew bundle cleanup --file=Brewfile.personal
|
||||
```
|
||||
|
||||
If the output looks good, then force the cleanup:
|
||||
|
||||
```bash
|
||||
$ brew bundle cleanup --force --file=Brewfile.personal
|
||||
```
|
||||
|
||||
See `brew cleanup --help` and `brew bundle --help` for more details.
|
||||
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.
|
||||
14
chrome/open-current-tab-in-new-window-with-vimium.md
Normal file
14
chrome/open-current-tab-in-new-window-with-vimium.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Open Current Tab In New Window With Vimium
|
||||
|
||||
Sometime I have a busy Chrome window going with a bunch of tabs open for
|
||||
various lines of work as well as a number of tabs that I've neglected to close.
|
||||
I then open a new tab, find something useful, and realize I'm at a "branching
|
||||
point". I'm about to start in on a specific chunk of work that will probably
|
||||
involve opening several more tabs and switch back and forth between some
|
||||
dashboards. I want to start all of this from a fresh slate -- or at least from
|
||||
a fresh Chrome window.
|
||||
|
||||
With [Vimium](https://github.com/philc/vimium), I can hit `W` (`Shift-w`) to
|
||||
have the current tab move from the current window to a new window. The original
|
||||
window, minus that one tab, will be left as is so that I can go back to it as
|
||||
needed.
|
||||
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.
|
||||
32
cursor/allow-cursor-to-be-launched-from-cli.md
Normal file
32
cursor/allow-cursor-to-be-launched-from-cli.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Allow Cursor To Be Launched From CLI
|
||||
|
||||
It is nice to be able to open Cursor for a specific project directly from the
|
||||
terminal like so:
|
||||
|
||||
```bash
|
||||
$ cd ~/dev/my/project
|
||||
|
||||
$ cursor .
|
||||
```
|
||||
|
||||
For the `cursor` launcher binary to be available like that, we have to find it
|
||||
and add it to the path.
|
||||
|
||||
It is probably located in the `/Applications` folder and within that nested down
|
||||
a couple directories is a `bin` directory that contains the binary we're looking
|
||||
for.
|
||||
|
||||
```bash
|
||||
ls /Applications/Cursor.app/Contents/Resources/app/bin
|
||||
bin/
|
||||
├── code*
|
||||
├── cursor*
|
||||
└── cursor-tunnel*
|
||||
```
|
||||
|
||||
The `cursor` binary is what we want, so let's add that to our path. In my case,
|
||||
I'll add this to my `~/.zshrc` file.
|
||||
|
||||
```bash
|
||||
export PATH="/Applications/Cursor.app/Contents/Resources/app/bin:$PATH"
|
||||
```
|
||||
4
dprint.json
Normal file
4
dprint.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"excludes": ["README.md"],
|
||||
"plugins": ["https://plugins.dprint.dev/markdown-0.16.0.wasm"]
|
||||
}
|
||||
@@ -9,10 +9,10 @@ test runs. Most of these files are tracked (already checked in to the
|
||||
repository). There are also many new files generated as part of the most recent
|
||||
test run.
|
||||
|
||||
I want to staging the changes to files that are already tracked, but hold off
|
||||
on doing anything with the new files.
|
||||
I want to stage the changes to files that are already tracked, but hold off on
|
||||
doing anything with the new files.
|
||||
|
||||
Running `git add spec/cassettes` won't do the track because that will pull in
|
||||
Running `git add spec/cassettes` won't do the trick because that will pull in
|
||||
everything. Running `git add --patch spec/cassettes` will take long and be
|
||||
tedious. Instead what I want is the `-u` flag. It's short for _update_ which
|
||||
means it will only stage already tracked files.
|
||||
|
||||
38
git/check-if-a-file-has-changed-in-a-script.md
Normal file
38
git/check-if-a-file-has-changed-in-a-script.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Check If A File Has Changed In A Script
|
||||
|
||||
If I'm at the command line and I want to check if a file has changed, I can run
|
||||
`git diff` and see what has changed. If I want to be more specific, I can run
|
||||
`git diff README.md` to see if there are changes to that specific file.
|
||||
|
||||
If I'm trying to do this check in a script though, I want the command to clearly
|
||||
tell the script _Yes_ or _No_. Usually a script looks for an exit code to
|
||||
determine what path to take. But as long as `git diff` runs successfully,
|
||||
regardless of whether or not their are changes, it is going to have an
|
||||
affirmative exit code of `0`.
|
||||
|
||||
This is why `git diff` offers the `--exit-code` flag.
|
||||
|
||||
> Make the program exit with codes similar to diff(1). That is, it exits with 1
|
||||
> if there were differences and 0 means no differences.
|
||||
|
||||
With that in mind, we can wire up a script with `git diff` that takes different
|
||||
paths depending on whether or not there are changes.
|
||||
|
||||
```bash
|
||||
if ! git diff --exit-code README.md; then
|
||||
echo "README.md has changes"
|
||||
else
|
||||
echo "README.md is clean"
|
||||
fi
|
||||
```
|
||||
|
||||
We can take this a step further and instead use the `--quiet` flag.
|
||||
|
||||
> Disable all output of the program. Implies --exit-code. Disables execution of
|
||||
> external diff helpers whose exit code is not trusted
|
||||
|
||||
This exhibits the same behavior as `--exit-code` and goes the additional step of
|
||||
silencing diff output and disabling execution of external diff helpers like
|
||||
`delta`.
|
||||
|
||||
See `man git-diff` for more details.
|
||||
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.
|
||||
35
git/cherry-pick-multiple-commits-at-once.md
Normal file
35
git/cherry-pick-multiple-commits-at-once.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Cherry Pick Multiple Commits At Once
|
||||
|
||||
I've always thought of `git cherry-pick` as being a command that you can run
|
||||
against a single commit by specifying the SHA of that commit. That's how I've
|
||||
always used it.
|
||||
|
||||
The man page for `git-cherry-pick` plainly states:
|
||||
|
||||
> Given one or more existing commits, apply the change each one introduces,
|
||||
> recording a new commit for each.
|
||||
|
||||
We can cherry pick multiple commits at once in a single command. They will be
|
||||
applied one at a time in the order listed.
|
||||
|
||||
Here we can see an example of applying two commits to the current branch and
|
||||
the accompanying output as they are auto-merged.
|
||||
|
||||
```bash
|
||||
$ git cherry-pick 5206af5 6362f41
|
||||
Auto-merging test/services/event_test.rb
|
||||
[jb/my-feature-branch 961f3deb] Use the other testing syntax
|
||||
Date: Fri May 2 10:50:14 2025 -0500
|
||||
1 file changed, 7 insertions(+), 7 deletions(-)
|
||||
Auto-merging test/services/event_test.rb
|
||||
[jb/my-feature-branch b15835d0] Make other changes to the test
|
||||
Date: Fri May 2 10:54:48 2025 -0500
|
||||
1 file changed, 7 insertions(+), 7 deletions(-)
|
||||
```
|
||||
|
||||
If the commits cannot be cleanly merged, then you may need to do some manual
|
||||
resolution as they are applied. Or maybe you want to try including the
|
||||
`-Xpatience` merge strategy.
|
||||
|
||||
See `man git-cherry-pick` for more details. Make sure to look at the _Examples_
|
||||
section which contains much more advanced examples beyond what is shown above.
|
||||
26
git/clear-entries-from-git-stash.md
Normal file
26
git/clear-entries-from-git-stash.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Clear Entries From Git Stash
|
||||
|
||||
I often stash changes as I'm moving between branches, rebasing, or pulling in
|
||||
changes from the remote. Usually these are changes that I will want to restore
|
||||
with a `git stash pop` in a few moments.
|
||||
|
||||
However, sometimes these stashed changes are abandoned to time.
|
||||
|
||||
When I run `git stash list` on an active project, I see that there are nine
|
||||
entries in the list. When I do `git show stash@{0}` and `git show stash@{1}` to
|
||||
see the changes that comprise the latest two entries, I don't see anything I
|
||||
care about.
|
||||
|
||||
I can get rid of those individual entries with, say, `git stash drop
|
||||
stash@{0}`.
|
||||
|
||||
But I'm pretty confident that I don't care about any of the nine entries in my
|
||||
stash list, so I want to _clear_ out all of them. I can do that with:
|
||||
|
||||
```bash
|
||||
$ git stash clear
|
||||
```
|
||||
|
||||
Now when I run `git stash list`, I see nothing.
|
||||
|
||||
See `man git-stash` for more details.
|
||||
@@ -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.
|
||||
32
git/exclude-a-directory-during-a-command.md
Normal file
32
git/exclude-a-directory-during-a-command.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Exclude A Directory During A Command
|
||||
|
||||
Many of the git commands we use, such as `git add`, `git restore`, etc., target
|
||||
files and paths relative to the current directory. This is typically exactly
|
||||
what we want, to stage and unstage and so forth the files and directories in
|
||||
front of us.
|
||||
|
||||
I recently ran into a situation where I needed to restore a small subset of
|
||||
changes. At the same time, I had a massive number of auto-generated files
|
||||
recording HTTP interactions (hundreds of files, modified on the working tree).
|
||||
I wanted to run a `git restore`, but wading through all those HTTP recording
|
||||
files was not feasible.
|
||||
|
||||
I needed to exclude those files. They all belonged to a `spec/cassettes`
|
||||
directory. I could exclude them with a _pathspec_ magic signature pattern which
|
||||
is used to alter and limit the paths in a git command.
|
||||
|
||||
A _pathspec_ magic signature is a special pattern made up of a `:` followed by
|
||||
some signature declaring what the pattern means.
|
||||
|
||||
The `(exclude)`, `!`, and `^` magic signatures all mean the same thing —
|
||||
exclude. So, we can exclude a directory from a `git restore` command like so:
|
||||
|
||||
```bash
|
||||
$ git restore --patch -- . ':!spec/cassettes'
|
||||
```
|
||||
|
||||
We've employed two pathspec patterns here. The first, `.`, scopes everything to
|
||||
the current directory. The second, `':!spec/cassettes'` excludes everything in
|
||||
the `spec/cassettes` directory.
|
||||
|
||||
See `man gitglossary` for more on _pathspecs_.
|
||||
44
git/highlight-small-change-on-single-line.md
Normal file
44
git/highlight-small-change-on-single-line.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Highlight Small Change On Single Line
|
||||
|
||||
Sometimes a change gets made on a single, long line of text in a Git tracked
|
||||
file. If it is a small, subtle change, then it can be hard to pick out when
|
||||
looking at the diff. A standard diff will show a green line of text stacked on
|
||||
a red line of text with no more granular information.
|
||||
|
||||
There are two ways we can improve the diff output in these situations.
|
||||
|
||||
The first is built-in to git. It is the `--word-diff` flag which will visually
|
||||
isolate the portions of the line that have changed.
|
||||
|
||||
```bash
|
||||
git diff --word-diff README.md
|
||||
```
|
||||
|
||||
Which will produce something like this:
|
||||
|
||||
```diff
|
||||
A collection of concise write-ups on small things I learn [-day to day-]{+day-to-day+} across a
|
||||
```
|
||||
|
||||
The outgoing part is wrapped in `[-...-]` and the incoming part is wrapped in
|
||||
`{+...+}`.
|
||||
|
||||
The second (and my preference) is to use
|
||||
[`delta`](https://github.com/dandavison/delta) as an external differ and pager
|
||||
for git.
|
||||
|
||||
```bash
|
||||
git -c core.pager=delta diff README.md
|
||||
```
|
||||
|
||||
I cannot visually demonstrate the difference in a standard code block. So I'll
|
||||
describe it. We see a red and green line stacked on each other, but with muted
|
||||
background colors. Then the specific characters that are different stand out
|
||||
because they are highlighted with brighter red and green. I [posted a visual
|
||||
here](https://bsky.app/profile/jbranchaud.bsky.social/post/3ln245orlxs2j).
|
||||
|
||||
`delta` can also be added as a standard part of your config like I demonstrate
|
||||
in [Better Diffs With Delta](git/better-diffs-with-delta.md).
|
||||
|
||||
h/t to [Dillon Hafer's post on
|
||||
`--word-diff`](https://til.hashrocket.com/posts/t994rwt3fg-finds-diffs-in-long-line)
|
||||
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.
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
While preparing some stats for a recent blog post on [A Decade of
|
||||
TILs](https://www.visualmode.dev/a-decade-of-tils), I ran into an issue
|
||||
referencing chuncks of time further back than 2020.
|
||||
referencing chunks of time further back than 2020.
|
||||
|
||||
```bash
|
||||
❯ git diff --diff-filter=A --name-only HEAD@{2016-02-06}..HEAD@{2017-02-06} -- "*.md"
|
||||
|
||||
20
git/restore-file-from-one-branch-to-the-current.md
Normal file
20
git/restore-file-from-one-branch-to-the-current.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Restore File From One Branch To The Current
|
||||
|
||||
On one feature branch I have created some files and made changes to some
|
||||
existing files as part of spiking a feature. Now I'm on a different branch
|
||||
taking another shot at it. I want changes from one or two of the files. In the
|
||||
past I've used `git-checkout` for this task. However, I believe this is one of
|
||||
the use cases they had in mind when they added `git-restore`.
|
||||
|
||||
What I want to do is _restore_ the state of a file as it appears on some source
|
||||
branch to my current branch. Here is what that looks like:
|
||||
|
||||
```bash
|
||||
$ git restore --source=some-feature-branch app/models/contact.rb
|
||||
```
|
||||
|
||||
Now when I check `git status` I'll see the state of that file on the
|
||||
`some-feature-branch` branch overlayed on my current working copy. If the file
|
||||
doesn't exist, it will be created.
|
||||
|
||||
See `man git-restore` for more details.
|
||||
56
git/set-up-gpg-signing-key.md
Normal file
56
git/set-up-gpg-signing-key.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Set Up GPG Signing Key
|
||||
|
||||
I wanted to have that "Verified" icon start showing up next to my commits in
|
||||
GitHub. To do that, I need to generate a GPG key, configure the secret key in
|
||||
GitHub, and then configure the public signing key with my git config.
|
||||
|
||||
```bash
|
||||
# generate a gpg key
|
||||
$ gpg --full-generate-key
|
||||
|
||||
# Pick the following options when prompted
|
||||
# - Choose "RSA and RSA" (Options 1)
|
||||
# - Max out key size at 4096
|
||||
# - Choose expiration date (e.g. 0 for no expiration)
|
||||
# - Enter "Real name" and "Email"
|
||||
(I matched those to what is in my global git config)
|
||||
# - Set passphrase (I had 1password generate a 4-word passphrase)
|
||||
```
|
||||
|
||||
It may take a few seconds to create.
|
||||
|
||||
I can see it was created by listing my GPG keys.
|
||||
|
||||
```bash
|
||||
$ gpg --list-secret-keys --keyid-format=long
|
||||
[keyboxd]
|
||||
---------
|
||||
sec rsa4096/1A8656918A8D016B 2025-10-19 [SC]
|
||||
...
|
||||
```
|
||||
|
||||
I'll need the `1A8656918A8D016B` portion of that response for the next command
|
||||
and it is what I set as my public signing key in my git config.
|
||||
|
||||
First, though, I add the full key block to my GitHub profile which I can copy
|
||||
like so:
|
||||
|
||||
```bash
|
||||
$ gpg --armor --export 1A8656918A8D016B | pbcopy
|
||||
```
|
||||
|
||||
And then I paste that as a new GPG Key on GitHub under _Settings_ -> _SSH and
|
||||
GPG Keys_.
|
||||
|
||||
Last, I update my global git config with the signing key and the preference to
|
||||
sign commits:
|
||||
|
||||
```bash
|
||||
git config --global user.signingkey 1A8656918A8D016B
|
||||
git config --global commit.gpgsign true
|
||||
```
|
||||
|
||||
Without `commit.gpgsign`, I would have to specify the `-S` flag every time I
|
||||
want to create a signed commit.
|
||||
|
||||
[source](https://git-scm.com/book/ms/v2/Git-Tools-Signing-Your-Work)
|
||||
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)
|
||||
19
github/open-a-pr-to-an-unforked-repo.md
Normal file
19
github/open-a-pr-to-an-unforked-repo.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Open A PR To An Unforked Repo
|
||||
|
||||
Sometimes I will clone a repo to explore the source code or to look into a
|
||||
potential bug. If my curiosity takes me far enough to make some changes, then I
|
||||
jump through the hoops of creating a fork, reconfiguring branches, pushing to my
|
||||
fork, and then opening the branch as a PR against the original repo.
|
||||
|
||||
The `gh` CLI allows me to avoid all that hoop-jumping. Directly from the cloned
|
||||
repo I can use `gh` to create a new PR. It will prompt me to creat a fork. If I
|
||||
accept, it will seamlessly create it and then open a PR from my fork to the
|
||||
original.
|
||||
|
||||
```bash
|
||||
$ gh pr create
|
||||
```
|
||||
|
||||
This allows me to create the PR with a few prompts from the CLI. If you prefer,
|
||||
you can include the `--web` flag to open the PR creation screen directly in the
|
||||
browser.
|
||||
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.
|
||||
38
github/tell-gh-what-the-default-repo-is.md
Normal file
38
github/tell-gh-what-the-default-repo-is.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Tell gh What The Default Repo Is
|
||||
|
||||
I recently forked [dkarter/dotfiles](https://github.com/dkarter/dotfiles) as a
|
||||
way of bootstrapping a robust dotfile config for a new machine that I could
|
||||
start making customizations to. I'm maintaining a `my-dotfiles` branch and keep
|
||||
things in sync with the original upstream repo.
|
||||
|
||||
When trying to go to *my* fork of the repo
|
||||
([jbranchaud/dotfiles](https://github.com/jbranchaud/dotfiles)) in the web with
|
||||
the `gh` CLI tool, I ran into a weird issue. It was instead opening up to
|
||||
`dkarter/dotfiles`.
|
||||
|
||||
`gh` was under the wrong impression which repo should be considered the default.
|
||||
To clarify things for `gh`, there is a command to set the default repo.
|
||||
|
||||
```bash
|
||||
$ gh repo set-default jbranchaud/dotfiles
|
||||
✓ Set jbranchaud/dotfiles as the default repository for the current directory
|
||||
```
|
||||
|
||||
Now when I run `gh repo view --web`, it opens the browser to my fork of the
|
||||
dotfiles.
|
||||
|
||||
But where does this setting live?
|
||||
|
||||
Opening this repo's `.git/config` file I can see a section for the `origin`
|
||||
remote that includes a new line for `gh-resolved`. This being set to `base`
|
||||
tells `gh` that this remote is the one to treat as the default repo.
|
||||
|
||||
```
|
||||
[remote "origin"]
|
||||
url = git@github.com:jbranchaud/dotfiles.git
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
gh-resolved = base
|
||||
|
||||
```
|
||||
|
||||
See `gh repo set-default --help` for more details.
|
||||
32
heroku/check-ruby-version-for-production-app.md
Normal file
32
heroku/check-ruby-version-for-production-app.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Check Ruby Version For Production App
|
||||
|
||||
While deploying a fresh Rails app to Heroku recently, I ran into an issue. The
|
||||
`it` block argument wasn't working despite being on Ruby 4.0. Or so I thought.
|
||||
|
||||
Running the following command reported the Ruby version of that Heroku server
|
||||
instance:
|
||||
|
||||
```bash
|
||||
❯ heroku run -- ruby --version
|
||||
Running ruby --version on ⬢ my-app... up, run.3090
|
||||
ruby 3.3.9 (2025-07-24 revision f5c772fc7c) [x86_64-linux]
|
||||
```
|
||||
|
||||
I was on `3.3.9` which must have been the fallback default at the time.
|
||||
|
||||
Though I had set the Ruby version in my `.ruby-version` file, I had neglected to
|
||||
specify it in the `Gemfile` as well. Once I added it to the `Gemfile` and
|
||||
redeployed, my Heroku server instance was running the expected version of Ruby.
|
||||
|
||||
```bash
|
||||
❯ heroku run -- ruby --version
|
||||
Running ruby --version on ⬢ my-app... up, run.5353
|
||||
ruby 4.0.0 (2025-12-25 revision 553f1675f3) +PRISM [x86_64-linux]
|
||||
```
|
||||
|
||||
Note: because [I have set `HEROKU_ORGANIZATION` and
|
||||
`HEROKU_APP`](set-default-team-and-app-for-project.md) in my environment
|
||||
(`.envrc`) for the local copy of the app, I don't need to specify those when
|
||||
running the `heroku run` command above.
|
||||
|
||||
See `heroku run --help` for more details.
|
||||
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.
|
||||
39
html/allow-number-input-to-accept-decimal-values.md
Normal file
39
html/allow-number-input-to-accept-decimal-values.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Allow Number Input To Accept Decimal Values
|
||||
|
||||
Here is a number input element:
|
||||
|
||||
```html
|
||||
<input type="number" id="amount" required class="border" />
|
||||
```
|
||||
|
||||
This renders an empty number input box with up and down arrows which will, by
|
||||
default, increment or decrement the value by **1**.
|
||||
|
||||
Of course, I can manually edit the input typing in a value like `1.25`.
|
||||
|
||||
However, when I submit that via an HTML form, the submission will be prevented
|
||||
and the browser will display a validation error.
|
||||
|
||||
> Please enter a valid value. The two nearest valid values are 1 and 2.
|
||||
|
||||
If I want to be able to input a decimal value like this, I need to change the
|
||||
`step` value. It defaults to `1`, but I could change it to `2`, `10`, or in
|
||||
this case to `0.01`.
|
||||
|
||||
```html
|
||||
<input type="number" step="0.01" id="amount" required class="border" />
|
||||
```
|
||||
|
||||
Notice now that as you click the up and down arrows, the value is incremented
|
||||
and decremented by **0.01** at a time.
|
||||
|
||||
If I want to maintain the step value of `1` while allowing decimal values, I
|
||||
can instead set the `step` value to be `any`.
|
||||
|
||||
```html
|
||||
<input type="number" step="any" id="amount" required class="border" />
|
||||
```
|
||||
|
||||
See the [MDN docs on number
|
||||
inputs](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/input/number)
|
||||
for more details.
|
||||
42
javascript/format-a-list-of-items-by-locale.md
Normal file
42
javascript/format-a-list-of-items-by-locale.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Format A List Of Items By Locale
|
||||
|
||||
The `Intl` module includes a [`ListFormat`
|
||||
object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/ListFormat)
|
||||
which can be used to format a list of items in a consistent way across locales.
|
||||
|
||||
I've reinvented the wheel of writing a helper function numerous times across
|
||||
projects for formatting a list of items that accounts for formatting based on
|
||||
how many items there are. This built-in function handles that with the added
|
||||
benefit of working across locales.
|
||||
|
||||
Here are lists of three, two, and one items formatted in the `long` styles for
|
||||
US english.
|
||||
|
||||
```javascript
|
||||
> const formatter = new Intl.ListFormat('en', { style: 'long', type: 'conjunction' });
|
||||
undefined
|
||||
|
||||
> formatter.format(['Alice', 'Bob', 'Carla'])
|
||||
'Alice, Bob, and Carla'
|
||||
|
||||
> formatter.format(['Coffee', 'Tea'])
|
||||
'Coffee and Tea'
|
||||
|
||||
> formatter.format(['Taco'])
|
||||
'Taco'
|
||||
```
|
||||
|
||||
The difference between `long` and `short` style for a `conjunction` is _and_
|
||||
versus _&_. In addition to the type`conjunction`, you could also use
|
||||
`disjunction` which will do an _or_ instead of an _and_. I'm not sure what
|
||||
you'd use the `unit` type for.
|
||||
|
||||
You could use another locale, such as French, as well:
|
||||
|
||||
```javascript
|
||||
> const formatter = new Intl.ListFormat('fr', { style: 'long', type: 'conjunction' });
|
||||
undefined
|
||||
|
||||
> formatter.format(['café', 'thé'])
|
||||
'café et thé'
|
||||
```
|
||||
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.
|
||||
27
mac/capture-screenshot-to-clipboard-from-cli.md
Normal file
27
mac/capture-screenshot-to-clipboard-from-cli.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Capture Screenshoot To Clipboard From CLI
|
||||
|
||||
MacOS comes with a `screencapture` utility that you can run from the terminal
|
||||
to activate the built-in screenshot functionality on Mac.
|
||||
|
||||
Usually when I am taking a screenshot, I want to do something with it right
|
||||
away. Such as paste it into an application or group chat. The `-c` flag forces
|
||||
the screen capture to go the clipboard.
|
||||
|
||||
I also generally want to capture a specific area of the screen so that the
|
||||
captured image includes the right amount of context and nothing more. The `-i`
|
||||
flag puts you in interactive screen capture mode. That means your cursor will
|
||||
turn into a crosshair that you can use to make a drag selection of the capture
|
||||
area.
|
||||
|
||||
```bash
|
||||
$ screencapture -ic
|
||||
```
|
||||
|
||||
Select an area to capture, it's now on your clipboard, paste it where you need
|
||||
it.
|
||||
|
||||
Note: The first time you run this command, your terminal program (e.g. iTerm2)
|
||||
may prompt you for the necessary OS permissions in order to capture images of
|
||||
your screen. You'll need to grant those permissions and then rerun the command.
|
||||
|
||||
See `man screencapture` for more details.
|
||||
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;
|
||||
```
|
||||
46
neovim/jump-between-changes-in-current-file.md
Normal file
46
neovim/jump-between-changes-in-current-file.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Jump Between Changes In Current File
|
||||
|
||||
With the [gitsigns.nvim plugin](https://github.com/lewis6991/gitsigns.nvim) for
|
||||
Neovim, I get some handy Git-related capabilities like gutter highlighting of
|
||||
additions, deletions, and changes to lines in the current file. These contiguous
|
||||
sections of modification to the versioned state of a file are called hunks.
|
||||
|
||||
Here are two mappings (in Lua) for gitsigns that allow me to jump to the next
|
||||
(`]h`) or previous (`[h`) hunk in the current file.
|
||||
|
||||
```lua
|
||||
---@type LazyKeysSpec[]
|
||||
M.gitsigns_mappings = {
|
||||
|
||||
-- Navigation
|
||||
{
|
||||
']h',
|
||||
function()
|
||||
if vim.wo.diff then
|
||||
vim.cmd.normal { ']c', bang = true }
|
||||
else
|
||||
require('gitsigns').nav_hunk 'next'
|
||||
end
|
||||
end,
|
||||
desc = 'Next Hunk',
|
||||
},
|
||||
|
||||
{
|
||||
'[h',
|
||||
function()
|
||||
if vim.wo.diff then
|
||||
vim.cmd.normal { '[c', bang = true }
|
||||
else
|
||||
require('gitsigns').nav_hunk 'prev'
|
||||
end
|
||||
end,
|
||||
desc = 'Prev Hunk',
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
This is particularly useful when I've just opened a big file and I want to jump
|
||||
directly to active changes in that file.
|
||||
|
||||
I got this mapping directly from [Dorian's
|
||||
dotfiles](https://github.com/dkarter/dotfiles).
|
||||
23
neovim/run-nvim-with-factory-defaults.md
Normal file
23
neovim/run-nvim-with-factory-defaults.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Run nvim With Factory Defaults
|
||||
|
||||
Most of the fun of using Neovim is tailoring it to your exact needs with custom
|
||||
configurations. Your configuration can be made up of environment variables,
|
||||
`init.lua`/`init.vim`, and user directories on the `runtimepath`.
|
||||
|
||||
Perhaps though, you want to load neovim with its "factory defaults". You want
|
||||
to ignore all your custom config and your _shada_ (shared data) file. I wanted
|
||||
to do just that recently to verify that neovim has the `ft-manpage` plugin
|
||||
enabled by default (as opposed to enabled somewhere in the labryinth of my
|
||||
config files).
|
||||
|
||||
The `--clean` flag does just this. It loads built-in plugins, but none of the
|
||||
user defined config.
|
||||
|
||||
```bash
|
||||
$ nvim --clean
|
||||
```
|
||||
|
||||
This is different than `nvim -u NONE` which excludes all plugins, including
|
||||
built-in ones.
|
||||
|
||||
See `man nvim` and `:help --clean` for more details.
|
||||
1
notes
Submodule
1
notes
Submodule
Submodule notes added at 897184eb02
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.
|
||||
39
postgres/check-the-size-of-databases-in-a-cluster.md
Normal file
39
postgres/check-the-size-of-databases-in-a-cluster.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Check The Size Of Databases In A Cluster
|
||||
|
||||
The `\l` command in `psql` will list all the databases for the server. The
|
||||
field surfaced by this meta-command are:
|
||||
|
||||
- Name
|
||||
- Owner
|
||||
- Encoding
|
||||
- Locale Provider
|
||||
- Collate
|
||||
- Ctype
|
||||
- ICU Locale
|
||||
- ICU Rules
|
||||
- Access privileges
|
||||
|
||||
If we add a `+`, issuing instead `\l+`, we get three additional fields:
|
||||
|
||||
- Size
|
||||
- Tablespace
|
||||
- Description
|
||||
|
||||
The _Size_ column is the human-formatted size of each database.
|
||||
|
||||
Another way to do this is with some SQL querying the underlying record keeping
|
||||
of the server's database.
|
||||
|
||||
```sql
|
||||
select
|
||||
db.datname as db_name,
|
||||
pg_size_pretty(pg_database_size(db.datname)) as db_size
|
||||
from pg_database db
|
||||
order by pg_database_size(db.datname) desc;
|
||||
```
|
||||
|
||||
Credit to [this StackOverflow
|
||||
answer](https://stackoverflow.com/a/18907188/535590) for how to do this with a
|
||||
SQL query.
|
||||
|
||||
[source](https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-META-COMMAND-LIST)
|
||||
58
postgres/create-and-execute-sql-statements-with-gexec.md
Normal file
58
postgres/create-and-execute-sql-statements-with-gexec.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Create And Execute SQL Statements With \gexec
|
||||
|
||||
The [`\gexec`
|
||||
meta-command](https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-META-COMMAND-GEXEC)
|
||||
is a variation of the [`\g`
|
||||
meta-command](https://www.postgresql.org/docs/current/app-psql.html#APP-PSQL-META-COMMAND-G),
|
||||
both of which can be used in a `psql` session. Whereas the `\g` command sends
|
||||
the current query in the buffer to the PostgreSQL server for execution, the
|
||||
`\gexec` command first sends the query to the server for execution and then
|
||||
executes each row of the result as its own SQL statement.
|
||||
|
||||
This is both a bit absurd and powerful. And a bit unnecessary considering all
|
||||
of the scripting capabilities with anything from bash to any language with a
|
||||
SQL client library.
|
||||
|
||||
Nevertheless, let's take a look at a contrived example of how it works. Here,
|
||||
we have a SQL statement that does some string concatenation based off values in
|
||||
an array. This results in three separate `create schema` statements.
|
||||
|
||||
```sql
|
||||
> select
|
||||
'create schema if not exists schema_' || letter || ';'
|
||||
from unnest(array['a', 'b', 'c']) as letter
|
||||
\gexec
|
||||
|
||||
CREATE SCHEMA
|
||||
CREATE SCHEMA
|
||||
CREATE SCHEMA
|
||||
|
||||
> \dn
|
||||
List of schemas
|
||||
Name | Owner
|
||||
----------+-------------------
|
||||
public | pg_database_owner
|
||||
schema_a | postgres
|
||||
schema_b | postgres
|
||||
schema_c | postgres
|
||||
(4 rows)
|
||||
```
|
||||
|
||||
Three new schemas get created which we can inspect with `\dn`.
|
||||
|
||||
Notice, if we simply execute the primary statement, we can see the intermediate
|
||||
result that `\gexec` will subsequently execute.
|
||||
|
||||
```sql
|
||||
> select
|
||||
'create schema if not exists schema_' || letter || ';'
|
||||
from unnest(array['a', 'b', 'c']) as letter
|
||||
\g
|
||||
|
||||
?column?
|
||||
---------------------------------------
|
||||
create schema if not exists schema_a;
|
||||
create schema if not exists schema_b;
|
||||
create schema if not exists schema_c;
|
||||
(3 rows)
|
||||
```
|
||||
37
postgres/references-target-primary-key-by-default.md
Normal file
37
postgres/references-target-primary-key-by-default.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# References Target Primary Key By Default
|
||||
|
||||
Typically when I am creating a table or adding a column that involves a foreign
|
||||
key constraint, I explicitly name the reference column.
|
||||
|
||||
```sql
|
||||
create table contacts (
|
||||
id int generated always as identity primary key,
|
||||
user_id int references users(id);
|
||||
);
|
||||
```
|
||||
|
||||
The [Create Table PostgreSQL
|
||||
Docs](https://www.postgresql.org/docs/17/sql-createtable.html) point out that
|
||||
specifying the reference column isn't strictly necessary.
|
||||
|
||||
> These clauses specify a foreign key constraint, which requires that a group
|
||||
> of one or more columns of the new table must only contain values that match
|
||||
> values in the referenced column(s) of some row of the referenced table. If
|
||||
> the refcolumn list is omitted, the primary key of the reftable is used.
|
||||
|
||||
If we're using the primary key as the reference column, then we can choose to
|
||||
omit the reference column.
|
||||
|
||||
```sql
|
||||
create table contacts (
|
||||
id int generated always as identity primary key,
|
||||
user_id int references users;
|
||||
);
|
||||
```
|
||||
|
||||
In the same way we can do this when adding a column.
|
||||
|
||||
```sql
|
||||
alter table contacts
|
||||
add column account_id int references account;
|
||||
```
|
||||
35
postgres/set-up-a-project-local-cluster-with-postgres-app.md
Normal file
35
postgres/set-up-a-project-local-cluster-with-postgres-app.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Set Up A Project-Local Cluster With Postgres.app
|
||||
|
||||
I want to set up a PostgreSQL cluster in my project directory. This helps
|
||||
provide some separation and clarity that this cluster and its databases are just
|
||||
for this project.
|
||||
|
||||
This can be done with `Postgres.app` (on Mac) hitting the `+` button in the
|
||||
bottom left corner of the app. This will pop open a "Create new server" modal.
|
||||
|
||||
From there, you'll want to give the server a name that you can identify within
|
||||
`Postgres.app`. E.g. "<App Name> Cluster"
|
||||
|
||||
Then select the Postgres version. My existing project is still on 17, so I'll
|
||||
select that.
|
||||
|
||||
The not so intuitive part is the _Data Directory_. Use the "Choose..." file
|
||||
picker to find the root directory of your project. Select that. Then click into
|
||||
the text input for the data directory and append the name of the data directory
|
||||
_to be created_ to that path. If I want it to all go in `postgres-data`, then my
|
||||
path will look like:
|
||||
|
||||
```
|
||||
/Users/me/dev/my-app/postgres-data
|
||||
```
|
||||
|
||||
The `postgres-data` directory doesn't exist yet. But it will in a moment.
|
||||
|
||||
You probably want the default port, so leave that at `5432` unless you know
|
||||
otherwise.
|
||||
|
||||
Click `Create server`, though that won't actually create the server yet. Now
|
||||
with that server selected in `Postgres.app` click the `Initialize` button. That
|
||||
will create the `postgres-data` directory and then run `initdb` under the hood
|
||||
which will add everything your server needs. It will now be running at that
|
||||
port, ready to connect.
|
||||
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.
|
||||
41
rails/customize-template-for-new-schema-migration.md
Normal file
41
rails/customize-template-for-new-schema-migration.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Customize Template For New Schema Migration
|
||||
|
||||
Rails has a set of generator functionality that we can use to scaffold entire
|
||||
slices of an app all the way down to generating a single migration file.
|
||||
|
||||
```bash
|
||||
$ rails generate migration MakeUserStatusColumnNotNull
|
||||
```
|
||||
|
||||
When we run a migration generator command like that, Rails reaches for the
|
||||
[baked-in migration
|
||||
template](https://github.com/rails/rails/blob/92be9af152f721588b7414119c931ea92930947b/activerecord/lib/rails/generators/active_record/migration/templates/migration.rb.tt)
|
||||
and creates a new migration file based on the given name and any other local
|
||||
variables that get set internally.
|
||||
|
||||
That's the standard behavior. However, we can override the migration template
|
||||
by defining our own template in `lib/templates/migration.rb.tt` of our Rails
|
||||
app. We'll need to follow the basic structure, but then we can alter it to our
|
||||
needs.
|
||||
|
||||
For instance, I typically like to use the `#up` and `#down` methods and write
|
||||
raw SQL for my migrations. To help with that this template provides a good
|
||||
starting point.
|
||||
|
||||
```ruby
|
||||
class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
|
||||
def up
|
||||
execute <<~SQL
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
execute <<~SQL
|
||||
SQL
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
We can see in
|
||||
[`migration_generator.rb`](https://github.com/rails/rails/blob/92be9af152f721588b7414119c931ea92930947b/activerecord/lib/rails/generators/active_record/migration/migration_generator.rb#L26-L43)
|
||||
how locals get set and what template gets chosen.
|
||||
43
rails/filter-active-model-validation-errors.md
Normal file
43
rails/filter-active-model-validation-errors.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Filter ActiveModel Validation Errors
|
||||
|
||||
Now that `ActiveModel` has a custom `Errors` class (as of Rails 6.1) instead of
|
||||
a hash, we get some useful functionality. Namely, we get a [`#where`
|
||||
method](https://api.rubyonrails.org/classes/ActiveModel/Errors.html#method-i-where)
|
||||
that allows us to filter errors based on the attribute name, type of
|
||||
validation, and even properties of that validation.
|
||||
|
||||
Here I have created a new `Book` without any attributes. All of its validations
|
||||
are going to fail and we are going to have an `ActiveModel::Errors` object
|
||||
attached to it with several errors.
|
||||
|
||||
```ruby
|
||||
> book = Book.new
|
||||
=>
|
||||
#<Book:0x00000001110397a8
|
||||
...
|
||||
> book.valid?
|
||||
=> false
|
||||
> book.errors
|
||||
=> #<ActiveModel::Errors [#<ActiveModel::Error attribute=added_by, type=blank, options={:message=>:required, :if=>#<Proc:0x0000000110096260 /Users/jbranchaud/.asdf/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/activerecord-7.2.1/lib/active_record/associations/builder/belongs_to.rb:130 (lambda)>}>, #<ActiveModel::Error attribute=title, type=blank, options={}>, #<ActiveModel::Error attribute=title, type=too_short, options={:count=>3}>, #<ActiveModel::Error attribute=author, type=blank, options={}>, #<ActiveModel::Error attribute=publication_date, type=blank, options={}>]>
|
||||
```
|
||||
|
||||
Let's say I want to check for a specific validation error. I can use `#where`
|
||||
to filter down by attribute name (e.g. `:title`). I can filter even further by
|
||||
including the validation type as well (e.g. `:too_short`).
|
||||
|
||||
```ruby
|
||||
> book.errors.where(:title)
|
||||
=>
|
||||
[#<ActiveModel::Error attribute=title, type=blank, options={}>,
|
||||
#<ActiveModel::Error attribute=title, type=too_short, options={:count=>3}>]
|
||||
> book.errors.where(:title, :too_short)
|
||||
=> [#<ActiveModel::Error attribute=title, type=too_short, options={:count=>3}>]
|
||||
> book.errors.where(:title, :too_short).first.message
|
||||
=> "is too short (minimum is 3 characters)"
|
||||
> book.errors.where(:title, :too_short).first.full_message
|
||||
=> "Title is too short (minimum is 3 characters)"
|
||||
```
|
||||
|
||||
This filtering could be used as part of conditional checks for what flash
|
||||
message gets displayed to the user or even what route/view gets rendered in
|
||||
response to the error.
|
||||
49
rails/format-datetime-with-builtin-formats.md
Normal file
49
rails/format-datetime-with-builtin-formats.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Format DateTime With Builtin Formats
|
||||
|
||||
The Rails [`Date`](https://api.rubyonrails.org/classes/Date.html)/`DateTime`
|
||||
and [`Time`](https://api.rubyonrails.org/classes/Time.html) classes each come
|
||||
with a `DATE_FORMATS` constant that is a hash of symbol names to format
|
||||
strings.
|
||||
|
||||
```ruby
|
||||
> DateTime::DATE_FORMATS
|
||||
=>
|
||||
{:short=>"%d %b",
|
||||
:long=>"%B %d, %Y",
|
||||
:db=>"%Y-%m-%d",
|
||||
:inspect=>"%Y-%m-%d",
|
||||
:number=>"%Y%m%d",
|
||||
:long_ordinal=>
|
||||
#<Proc:0x0000000105b2cef0 /Users/jbranchaud/.local/share/mise/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-8.0.1/lib/active_support/core_ext/date/conversions.rb:15 (lambda)>,
|
||||
:rfc822=>"%d %b %Y",
|
||||
:rfc2822=>"%d %b %Y",
|
||||
:iso8601=>
|
||||
#<Proc:0x0000000105b2cec8 /Users/jbranchaud/.local/share/mise/installs/ruby/3.2.2/lib/ruby/gems/3.2.0/gems/activesupport-8.0.1/lib/active_support/core_ext/date/conversions.rb:21 (lambda)>}
|
||||
```
|
||||
|
||||
These can be used as a standardized, ready-to-use, named formats when turning
|
||||
`DateTime` objects into strings.
|
||||
|
||||
Here are a few examples
|
||||
|
||||
```ruby
|
||||
> now = DateTime.now
|
||||
=> Wed, 30 Apr 2025 23:08:08 -0500
|
||||
|
||||
> now.to_fs(:long)
|
||||
=> "April 30, 2025 23:08"
|
||||
|
||||
> now.to_fs(:long_ordinal)
|
||||
=> "April 30th, 2025 23:08"
|
||||
|
||||
> now.to_fs(:iso8601)
|
||||
=> "2025-04-30T23:08:08-05:00"
|
||||
```
|
||||
|
||||
If an unrecognized key is passed to `#to_fs`, then it falls back to the
|
||||
`:iso8601` format.
|
||||
|
||||
```ruby
|
||||
> now.to_fs(:taco_bell)
|
||||
=> "2025-04-30T23:08:08-05:00"
|
||||
```
|
||||
39
rails/format-specific-html-erb-template-files.md
Normal file
39
rails/format-specific-html-erb-template-files.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Format Specific html.erb Template Files
|
||||
|
||||
There are a few tools out there that can do formatting of `*.html.erb` template
|
||||
files. One that I like is
|
||||
[nebulab/erb-formatter](https://github.com/nebulab/erb-formatter#readme)
|
||||
because it is ready to use in many different editors. That means that it is
|
||||
easier to adopt on a team of developers with different editor preferences.
|
||||
|
||||
That said, there are projects where I don't necessarily want it wired up to run
|
||||
on save because the formatting changesets will be too agressive. Instead, I
|
||||
want to run it manually on specific files as I see fit.
|
||||
|
||||
To do this, I install the formatter tool:
|
||||
|
||||
```bash
|
||||
$ gem install erb-formatter
|
||||
```
|
||||
|
||||
And now it is available as a CLI tool (try `which erb-format`).
|
||||
|
||||
As their docs recommend, I can run it against all files like so:
|
||||
|
||||
```
|
||||
$ erb-format app/views/**/*.html.erb --write
|
||||
```
|
||||
|
||||
If that is too aggressive though, I find it useful to either run against a
|
||||
specific file:
|
||||
|
||||
```
|
||||
$ erb-format app/views/some/model/index.html.erb --write
|
||||
```
|
||||
|
||||
Or when I'm wrapping up changes on a branch, I like to run it against all the
|
||||
view files that were touched on this branch:
|
||||
|
||||
```
|
||||
$ git diff --name-only master...HEAD -- app/views | xargs erb-format --write
|
||||
```
|
||||
55
rails/manage-timestamps-with-upsert.md
Normal file
55
rails/manage-timestamps-with-upsert.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Manage Timestamps With Upsert
|
||||
|
||||
Modern versions of Rails and ActiveRecord have [an `upsert`
|
||||
method](https://api.rubyonrails.org/v8.0.2/classes/ActiveRecord/Relation.html#method-i-upsert)
|
||||
which will, if available, use your database's upsert capability to either
|
||||
insert a new row or update an existing row based on the unique identifier.
|
||||
|
||||
The docs have the following disclaimer:
|
||||
|
||||
> It does not instantiate any models nor does it trigger Active Record
|
||||
> callbacks or validations. Though passed values go through Active Record’s
|
||||
> type casting and serialization.
|
||||
|
||||
It's a bit different to work with than other ActiveRecord methods. It left me
|
||||
wondering if it would handle timestamp management or if I would have to do that
|
||||
myself.
|
||||
|
||||
Let's upsert a new record into `books`:
|
||||
|
||||
```ruby
|
||||
> Book.upsert({title: "Shogun", author: "James Clavell", added_by_id: 1, publication_date: Date.today}, unique_by: :id)
|
||||
=> #<ActiveRecord::Result:0x00000001141c3df0 ...
|
||||
|
||||
> Book.select(:id, :title, :created_at, :updated_at).last
|
||||
=> #<Book:0x0000000113bae898 id: 12, title: "Shogun", created_at: "2025-06-26 14:08:26.035633000 +0000", updated_at: "2025-06-26 14:08:26.035633000 +0000">
|
||||
```
|
||||
|
||||
Notice that the `created_at` and `updated_at` timestamps get set.
|
||||
|
||||
Now, let's upsert the record (notice we're passing in the `id`) to update the title with an `ō`.
|
||||
|
||||
```ruby
|
||||
> Book.upsert({id: 12, title: "Shōgun", author: "James Clavell", added_by_id: 1, publication_date: Date.today}, unique_by: :id)
|
||||
=> #<ActiveRecord::Result:0x0000000113cace98 ...
|
||||
|
||||
> Book.select(:id, :title, :created_at, :updated_at).last
|
||||
=> #<Book:0x00000001143aadd0 id: 12, title: "Shōgun", created_at: "2025-06-26 14:08:26.035633000 +0000", updated_at: "2025-06-26 14:10:46.280480000 +0000">
|
||||
```
|
||||
|
||||
Notice that the `updated_at` gets set to a time about 2 minutes later.
|
||||
|
||||
Lastly let's look at the `record_timestamps` option. This is `nil` by default
|
||||
which means the underlying methods default kicks in which _is_ to record the
|
||||
timestamps. We can override that behavior by passing in `false`.
|
||||
|
||||
```ruby
|
||||
> Book.upsert({id: 12, title: "Shōgun, Part 2", author: "James Clavell", added_by_id: 1, publication_date: Date.today}, unique_by: :id, record_timestamps: false)
|
||||
=> #<ActiveRecord::Result:0x0000000113989428 ...
|
||||
|
||||
> Book.select(:id, :title, :created_at, :updated_at).last
|
||||
=> #<Book:0x0000000114fe1b80 id: 12, title: "Shōgun, Part 2", created_at: "2025-06-26 14:08:26.035633000 +0000", updated_at: "2025-06-26 14:10:46.280480000 +0000">
|
||||
```
|
||||
|
||||
Notice that the `updated_at` value doesn't change between this upsert and the
|
||||
previous upsert.
|
||||
40
rails/parameterize-a-string-with-underscores.md
Normal file
40
rails/parameterize-a-string-with-underscores.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Parameterize A String With Underscores
|
||||
|
||||
I have human-readable status strings that I'm working with like `In progress`,
|
||||
`Pending approval`, and `Completed`. I need to deterministically turn those
|
||||
into parameterized values that I can compare. That is, I want them lowercased
|
||||
and separated by underscores instead of spaces.
|
||||
|
||||
The `ActiveSupport` `#parameterize` method, as is, gets me pretty close.
|
||||
|
||||
```ruby
|
||||
> statuses = [
|
||||
"In progress",
|
||||
"Pending approval",
|
||||
"Completed"
|
||||
]
|
||||
|
||||
> statuses.map(&:parameterize)
|
||||
=> [
|
||||
"in-progress",
|
||||
"pending-approval",
|
||||
"completed"
|
||||
]
|
||||
```
|
||||
|
||||
Those are separated by dashes though. Fortunately, `parameterize` takes a
|
||||
`separator` option that we can use to verride what character is used to
|
||||
separate words. Let's use an underscore (`_`).
|
||||
|
||||
```ruby
|
||||
> statuses.map { |str| str.parameterize(separator: '_') }
|
||||
=> [
|
||||
"in_progress",
|
||||
"pending_approval",
|
||||
"completed"
|
||||
]
|
||||
```
|
||||
|
||||
See the [`#paramterize`
|
||||
docs](https://api.rubyonrails.org/classes/ActiveSupport/Inflector.html#method-i-parameterize)
|
||||
for more details.
|
||||
50
rails/prevent-mailer-previews-from-cluttering-database.md
Normal file
50
rails/prevent-mailer-previews-from-cluttering-database.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Prevent Mailer Previews From Cluttering Database
|
||||
|
||||
ActionMailer Previews give you a way to view email templates that your system
|
||||
sends. This is how I check that it is styled properly and that the logic and
|
||||
data of the template are able to run and render.
|
||||
|
||||
Data for a preview typically means we need ActiveRecord objects and even their
|
||||
associations. If we start creating one-off records in our previews either with
|
||||
`#create` or with something like `FactoryBot`, those records will get left
|
||||
behind in our development database. Every view and refresh of a preview will
|
||||
generate more of these records.
|
||||
|
||||
One way to get around that is to use `#new` and `#build`. I've found this
|
||||
cumbersome and it often leaves assocations missing or inaccessible.
|
||||
|
||||
What if instead the preview could clean up after itself? That sounds like a
|
||||
great job for a database transaction.
|
||||
|
||||
Let's create a `test/mailers/previews/base_preview.rb` as a base class for all
|
||||
our preview classes.
|
||||
|
||||
```ruby
|
||||
class BasePreview < ActionMailer::Preview
|
||||
def self.call(...)
|
||||
message = nil
|
||||
ActiveRecord::Base.transaction do
|
||||
message = super(...)
|
||||
raise ActiveRecord::Rollback
|
||||
end
|
||||
message
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
This wraps the existing `self.call` functionality in a transaction that
|
||||
collects the resulting message from the preview and then rolls back the
|
||||
database changes.
|
||||
|
||||
Now, instead of our individual preview classes inheriting directly from
|
||||
`ActionMailer::Preview`, they can inherit from `BasePreview`.
|
||||
|
||||
```ruby
|
||||
class UserMailer < BasePreview
|
||||
|
||||
# ...
|
||||
|
||||
end
|
||||
```
|
||||
|
||||
[source](https://stackoverflow.com/a/31289295)
|
||||
40
rails/provide-fake-form-helper-to-controllers.md
Normal file
40
rails/provide-fake-form-helper-to-controllers.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Provide Fake Form Helper To Controllers
|
||||
|
||||
I'm rendering a partial from a turbo stream. The partial is meant to be
|
||||
rendered within a Rails form object because it contains an input element that
|
||||
needs to reference the form object. The problem is that from the controller
|
||||
that is streaming the partial, there is no
|
||||
[FormBuilder](https://api.rubyonrails.org/v6.1.0/classes/ActionView/Helpers/FormBuilder.html)
|
||||
object.
|
||||
|
||||
One way to get around this that I've borrowed from [Justin
|
||||
Searls](https://justin.searls.co/posts/instantiate-a-custom-rails-formbuilder-without-using-form_with/)
|
||||
is with a `FauxFormHelper`.
|
||||
|
||||
```ruby
|
||||
module FauxFormHelper
|
||||
FauxFormObject = Struct.new do
|
||||
def errors
|
||||
end
|
||||
|
||||
def method_missing(...)
|
||||
end
|
||||
|
||||
def respond_to_missing?(...)
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def faux_form
|
||||
@faux_form ||= ActionView::Helpers::FormBuilder.new(
|
||||
nil,
|
||||
FauxFormObject.new,
|
||||
self,
|
||||
{}
|
||||
)
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
This module defines and exposes a `faux_form` object that controllers and views
|
||||
can access. Then my partial can recieve that form object as a parameter.
|
||||
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 "$@"
|
||||
```
|
||||
55
rails/scope-records-to-a-lower-or-upper-bound.md
Normal file
55
rails/scope-records-to-a-lower-or-upper-bound.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Scope Records To A Lower Or Upper Bound
|
||||
|
||||
Typically when we use
|
||||
[`#where`](https://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-where)
|
||||
to scope queries against ActiveRecord models, we are looking to do a direct
|
||||
"equals" comparison.
|
||||
|
||||
Such as `auth_codes.user_id = 1` in the example below.
|
||||
|
||||
```ruby
|
||||
> AuthCode.where(user_id: 1)
|
||||
AuthCode Load (0.4ms) SELECT "auth_codes".* FROM "auth_codes" WHERE "auth_codes"."user_id" = 1 /* loading for pp */ LIMIT 11
|
||||
```
|
||||
|
||||
We can do more powerful things with `#where` (assuming your database supports
|
||||
it, in my case PostgreSQL), such as comparing over ranges of dates. Ruby's
|
||||
range syntax gives us an elegant way to express ranges.
|
||||
|
||||
```ruby
|
||||
> 2..10 # range with lower bound of 2 and upper bound of 10
|
||||
|
||||
> 2.. # 'end'less range
|
||||
|
||||
> ..10 # 'begin'less range
|
||||
```
|
||||
|
||||
These latter two examples are ranges that are unbounded on one side or the
|
||||
other. We can use these in ActiveRecord `#where` queries to do "greater than or
|
||||
equal to" and "less than or equal to" conditionals.
|
||||
|
||||
And we can do the same with ranges of dates like in the following queries.
|
||||
|
||||
|
||||
```ruby
|
||||
> AuthCode.where(created_at: 10.days.ago..).count
|
||||
AuthCode Count (97.1ms) SELECT COUNT(*) FROM "auth_codes" WHERE "auth_codes"."created_at" >= '2025-09-24 00:35:46.937715'
|
||||
|
||||
> AuthCode.where(created_at: 10.days.ago..5.days.ago).count
|
||||
AuthCode Count (0.6ms) SELECT COUNT(*) FROM "auth_codes" WHERE "auth_codes"."created_at" BETWEEN '2025-09-24 00:35:59.901441' AND '2025-09-29 00:35:59.901512'
|
||||
|
||||
> AuthCode.where(created_at: ..5.days.ago).count
|
||||
AuthCode Count (0.3ms) SELECT COUNT(*) FROM "auth_codes" WHERE "auth_codes"."created_at" <= '2025-09-29 00:36:09.731444'
|
||||
```
|
||||
|
||||
Notice in the generated SQL how the simple `#where` method gets transformed
|
||||
into a `>=`, a `<=`, or a `between` clause.
|
||||
|
||||
And while dates are a powerful example of this, there is nothing to stop us
|
||||
from querying against other kinds of ranges like numeric ones.
|
||||
|
||||
```ruby
|
||||
# Orders under $10
|
||||
ten_dollars_in_cents = 10 * 100
|
||||
Order.where.not(fulfilled_at: nil).where(amount: ..ten_dollars_in_cents)
|
||||
```
|
||||
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.
|
||||
39
ruby/avoid-double-negation-with-minitest-refute.md
Normal file
39
ruby/avoid-double-negation-with-minitest-refute.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Avoid Double Negation With Minitest Refute
|
||||
|
||||
As I'm writing some tests for a recent feature, I end up with various test
|
||||
cases that make a variety of assertions.
|
||||
|
||||
```ruby
|
||||
assert_equal resp_body["id"], expected_id
|
||||
|
||||
# ...
|
||||
|
||||
assert_not resp_body["items"].empty?
|
||||
```
|
||||
|
||||
The first assertion reads pretty naturally. The second one requires some extra
|
||||
mental parsing because of the `_not` and then the "negative" check of
|
||||
`#empty?`.
|
||||
|
||||
Another way to express this that might read more naturally is with
|
||||
[`#refute`](https://ruby-doc.org/stdlib-3.0.2/libdoc/minitest/rdoc/Minitest/Assertions.html#method-i-refute).
|
||||
|
||||
```ruby
|
||||
refute resp_body["items"].empty?
|
||||
```
|
||||
|
||||
This says that we _refute_ that items is empty, so the assertion should fail if
|
||||
empty.
|
||||
|
||||
Ruby is flexible in other ways. We may also prefer to write it as:
|
||||
|
||||
```ruby
|
||||
assert resp_body["items"].present?
|
||||
```
|
||||
|
||||
Or we could even take advantage of a more specific variant of refute with
|
||||
[`#refute_empty`](https://ruby-doc.org/stdlib-3.0.2/libdoc/minitest/rdoc/Minitest/Assertions.html#method-i-refute_empty):
|
||||
|
||||
```ruby
|
||||
refute_empty resp_body["items"]
|
||||
```
|
||||
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.
|
||||
55
ruby/decompose-unicode-character-with-diacritic-mark.md
Normal file
55
ruby/decompose-unicode-character-with-diacritic-mark.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Decompose Unicode Character With Diacritic Mark
|
||||
|
||||
A character like the `ñ` is typically represented by the unicode codepoint of
|
||||
`U+00F1`. However, it is also possible to represent it with two unicode
|
||||
codepoints -- the `n` (`U+006E`) and the combining diacritical mark `˜`
|
||||
(`U+0303`).
|
||||
|
||||
We can see that by comparing a typed `ñ` with one where we split it apart into
|
||||
the separate codepoints. We can do that with
|
||||
[`#unicode_normalize`](https://apidock.com/ruby/v2_5_5/String/unicode_normalize)
|
||||
and the `:nfd` argument which stands for _Normalized Form Decomposed_.
|
||||
|
||||
```ruby
|
||||
> "ñ" == "ñ".unicode_normalize(:nfd)
|
||||
=> false
|
||||
> "ñ".unicode_normalize(:nfd).length
|
||||
=> 2
|
||||
> "ñ".length
|
||||
=> 1
|
||||
```
|
||||
|
||||
We can inspect the exact codepoints by iterating over each character and
|
||||
printing out the codepoint value.
|
||||
|
||||
```ruby
|
||||
"ñ".each_char.with_index do |char, i|
|
||||
puts "#{i}: '#{char}' -> U+#{char.ord.to_s(16).upcase.rjust(4, '0')}"
|
||||
end
|
||||
# 0: 'ñ' -> U+00F1
|
||||
# => "ñ"
|
||||
|
||||
"ñ".unicode_normalize(:nfd).each_char.with_index do |char, i|
|
||||
puts "#{i}: '#{char}' -> U+#{char.ord.to_s(16).upcase.rjust(4, '0')}"
|
||||
end
|
||||
# 0: 'n' -> U+006E
|
||||
# 1: '̃' -> U+0303
|
||||
#=> "ñ"
|
||||
```
|
||||
|
||||
Notice the difference after the character has been decomposed such that the
|
||||
diacritic is separated from the character.
|
||||
|
||||
This can be done with other characters containing diacritics.
|
||||
|
||||
And here we go the other direction with
|
||||
[`#pack`](https://ruby-doc.org/core-3.0.1/Array.html#method-i-pack).
|
||||
|
||||
```ruby
|
||||
> [0x006E, 0x0303].pack("U*")
|
||||
=> "ñ"
|
||||
> [0x00F1].pack("U*")
|
||||
=> "ñ"
|
||||
> [0x006E, 0x0303].pack("U*") == [0x00F1].pack("U*")
|
||||
=> false
|
||||
```
|
||||
44
ruby/get-specific-values-from-hashes-and-arrays.md
Normal file
44
ruby/get-specific-values-from-hashes-and-arrays.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Get Specific Values From Hashes And Arrays
|
||||
|
||||
Ruby defines a `#values_at` method on both `Hash` and `Array` that can be used
|
||||
to grab multiple values from an instance of either of those structures.
|
||||
|
||||
Here is an example of grabbing values by key (if they exist) from a hash.
|
||||
|
||||
```ruby
|
||||
> hash = {one: :two, hello: "world", four: 4}
|
||||
=> {one: :two, hello: "world", four: 4}
|
||||
> hash.values_at(:one, :four, :three)
|
||||
=> [:two, 4, nil]
|
||||
```
|
||||
|
||||
And here is an example of grabbing values at specific indexes from an array, if
|
||||
those indexes exist.
|
||||
|
||||
```ruby
|
||||
> arr = [:a, :b, :c, :d, :e]
|
||||
=> [:a, :b, :c, :d, :e]
|
||||
> arr.values_at(0, 3, 6)
|
||||
=> [:a, :d, nil]
|
||||
```
|
||||
|
||||
Notice that in both cases, `nil` is returned for a key or index that doesn't
|
||||
exist.
|
||||
|
||||
What I like about this method is that in a single call I can grab multiple
|
||||
named (or indexed) values and get a single array result with those values.
|
||||
|
||||
One way I might use this with a JSON response from an API request could look
|
||||
like this:
|
||||
|
||||
```ruby
|
||||
resp = client.getSomeData(id: 123)
|
||||
|
||||
[status, body] = resp.values_at("status", "body")
|
||||
|
||||
if status == 200
|
||||
puts body
|
||||
end
|
||||
```
|
||||
|
||||
[source](https://docs.ruby-lang.org/en/3.4/Hash.html#method-i-values_at)
|
||||
24
ruby/get-the-names-of-the-month.md
Normal file
24
ruby/get-the-names-of-the-month.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Get The Names Of The Month
|
||||
|
||||
Ruby's `Date` object has a `MONTHNAMES` constant that returns an array of names
|
||||
of the month. You'd think that means the array contains 12 items. However, the
|
||||
size of that array is 13.
|
||||
|
||||
```ruby
|
||||
> Date::MONTHNAMES
|
||||
=> [nil, "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
|
||||
```
|
||||
|
||||
Notice it has all 12 months, plus an initial value of `nil`.
|
||||
|
||||
This is because it allows us to more intuitive access a month by it's index
|
||||
without having to do a little subtraction. If I want to know what the 9th month
|
||||
is, I can do an array access for `9`.
|
||||
|
||||
```ruby
|
||||
> Date::MONTHNAMES[9]
|
||||
=> "September"
|
||||
```
|
||||
|
||||
Because arrays in Ruby use 0-based indexing, without this baked in `nil` value,
|
||||
you'd instead get `"October"` when passing in `9`.
|
||||
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>/`).
|
||||
72
tailwind/disable-and-enable-a-button.md
Normal file
72
tailwind/disable-and-enable-a-button.md
Normal file
@@ -0,0 +1,72 @@
|
||||
# Disable And Enable A Button
|
||||
|
||||
With TailwindCSS we can take a couple different approaches to tie the visual
|
||||
and functional interactivity of a button to another elements state using
|
||||
classes.
|
||||
|
||||
One approach is to use
|
||||
[`peer`](https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-sibling-state)
|
||||
and `peer-checked:<class>`.
|
||||
|
||||
```html
|
||||
<div>
|
||||
<input
|
||||
type="checkbox"
|
||||
id="peer-enable"
|
||||
class="w-5 h-5 cursor-pointer peer"
|
||||
/>
|
||||
<label
|
||||
for="peer-enable"
|
||||
class="cursor-pointer text-slate-700 font-medium"
|
||||
>
|
||||
I agree to the terms and conditions
|
||||
</label>
|
||||
<button
|
||||
class="opacity-40 pointer-events-none grayscale cursor-not-allowed peer-checked:opacity-100 peer-checked:pointer-events-auto peer-checked:grayscale-0 peer-checked:cursor-pointer"
|
||||
>
|
||||
Peer Submit
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
Classes to make to button appear disabled (e.g. `opacity-40`) as well as
|
||||
functional classes that affect interactivity (e.g. `pointer-events-none`) are
|
||||
applied by default. When the sibling checkbox gets checked, the inverted
|
||||
classes take effect making the button enabled.
|
||||
|
||||
The `peer` approach works, but lacks flexibility. As soon as I need to make any
|
||||
structural changes to the HTML that sever the peer (i.e. sibling) relationship
|
||||
of the checkbox and the button, those classes stop working.
|
||||
|
||||
With
|
||||
[`group`](https://tailwindcss.com/docs/hover-focus-and-other-states#styling-based-on-the-descendants-of-a-group)
|
||||
and `group-has-[:checked]:<class>`, I can style the button relative to another
|
||||
group member as long as everything is nested under some shared group tag.
|
||||
|
||||
```html
|
||||
<div class="group">
|
||||
<div class="flex items-center gap-3">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="group-enable"
|
||||
class="w-5 h-5 cursor-pointer"
|
||||
/>
|
||||
<label
|
||||
for="group-enable"
|
||||
class="cursor-pointer text-slate-700 font-medium"
|
||||
>
|
||||
I agree to the terms and conditions
|
||||
</label>
|
||||
</div>
|
||||
<button
|
||||
class="opacity-40 pointer-events-none grayscale cursor-not-allowed group-has-[:checked]:opacity-100 group-has-[:checked]:pointer-events-auto group-has-[:checked]:grayscale-0 group-has-[:checked]:cursor-pointer"
|
||||
>
|
||||
Group Submit
|
||||
</button>
|
||||
</div>
|
||||
```
|
||||
|
||||
We can even utilize [named
|
||||
groups](https://tailwindcss.com/docs/hover-focus-and-other-states#differentiating-nested-groups)
|
||||
if we have overlapping and conflicting group interactions. But I won't get into
|
||||
that here.
|
||||
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.
|
||||
31
taskfile/run-a-task-if-it-meets-criteria.md
Normal file
31
taskfile/run-a-task-if-it-meets-criteria.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Run A Task If It Meets Criteria
|
||||
|
||||
The [Taskfile `status`
|
||||
directive](https://taskfile.dev/docs/guide#limiting-when-tasks-run) can be used
|
||||
to tell a task when it needs to run. If it doesn't need to run, it can be
|
||||
skipped over. The idea being that we're making a status check to see if we're
|
||||
up-to-date or need to run the task.
|
||||
|
||||
For instance, here is a `status` check that determines if there are changes to
|
||||
commit and push. If there are changes to `NOTES.md`, then we are out-of-date and
|
||||
need to run the `cmds` that make up the task.
|
||||
|
||||
```yaml
|
||||
notes:push:
|
||||
desc: Commit and push changes to notes submodule
|
||||
dir: '{{.NOTES_DIR}}'
|
||||
cmds:
|
||||
- git add NOTES.md
|
||||
- git commit -m "Update notes - $(date '+%Y-%m-%d %H:%M')"
|
||||
- git push
|
||||
status:
|
||||
- git diff --exit-code NOTES.md
|
||||
silent: false
|
||||
```
|
||||
|
||||
This is useful because I don't want the `git add`, `git commit`, and `git push`
|
||||
commands to run when there is nothing to do.
|
||||
|
||||
Note: this is different from the `preconditions` directive. Instead of
|
||||
short-circuiting a sequence of tasks, this will either run or skip the task and
|
||||
move on to the next one.
|
||||
19
tmux/add-bindings-to-split-panes-to-current-directory.md
Normal file
19
tmux/add-bindings-to-split-panes-to-current-directory.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Add Bindings To Split Panes To Current Directory
|
||||
|
||||
When I am vertically or horizontally splitting a pane or opening another window,
|
||||
I generally want it to open to the same directory as I'm currently in. The
|
||||
default behavior in tmux is for those commands to open to the starting directory
|
||||
of the session.
|
||||
|
||||
Looking through the [tmux.conf in
|
||||
dkarter/dotfiles](https://github.com/dkarter/dotfiles/blob/master/config/tmux/tmux.conf#L109-L111),
|
||||
I found a good way to achieve the behavior I want.
|
||||
|
||||
```
|
||||
bind-key - split-window -v -c '#{pane_current_path}'
|
||||
bind-key \\ split-window -h -c '#{pane_current_path}'
|
||||
bind-key c new-window -c '#{pane_current_path}'
|
||||
```
|
||||
|
||||
What I like about this is that `-` (vertical) and `\` (horizontal) look visually
|
||||
like the splits they represent. Meanwhile, I leave `%` and `"` intact.
|
||||
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`.
|
||||
32
unix/authorize-a-curl-request.md
Normal file
32
unix/authorize-a-curl-request.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Authorize A cURL Request
|
||||
|
||||
When making a cURL request to an endpoint that requires authentication,
|
||||
sometimes you already have a bearer token and other times you have a username
|
||||
and password pair. If you have a bearer token, you can format a `Authorization`
|
||||
header with the `-H` flag that includes that value.
|
||||
|
||||
If you have a username and password for the API, you can instead use the `-u`
|
||||
flag. The `-u` flag will format the username and password, base64 encode it,
|
||||
and then add it as an `Authorization` header.
|
||||
|
||||
```bash
|
||||
$ curl -v -u "username:password" https://some-endpoint.com/api/v1/status
|
||||
|
||||
...
|
||||
> GET /api/v1/status HTTP/2
|
||||
> Host: some-endpoint.com
|
||||
> Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQK
|
||||
> User-Agent: curl/8.1.2
|
||||
...
|
||||
```
|
||||
|
||||
You can even pass in only the username to the `-u` flag and cURL will know to
|
||||
prompt you for the password. This is a nice way to avoid putting plain text
|
||||
passwords in your shell history.
|
||||
|
||||
```bash
|
||||
$ curl -v -u "username" https://some-endpoint.com/api/v1/status
|
||||
Enter host password for user 'username':
|
||||
```
|
||||
|
||||
See `man curl` for more details.
|
||||
38
unix/create-a-filename-with-the-current-date.md
Normal file
38
unix/create-a-filename-with-the-current-date.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Create A Filename With The Current Date
|
||||
|
||||
I was recently working on a script to pull a scrubbed database dump using the
|
||||
`pg_dump` Postgres utility. Ultimately, the script does something like this to
|
||||
dump a remote database to a local file:
|
||||
|
||||
```bash
|
||||
pg_dump \
|
||||
-h host.region.rds.amazonaws.com \
|
||||
-U db_username \
|
||||
-d db_name \
|
||||
-F c \
|
||||
-f scrubbed-database-$(date +%Y-%m-%d).dump
|
||||
```
|
||||
|
||||
Notice the last part of that command where we define the name of the dump file.
|
||||
It has a `$(...)` that is used to run and interpolate a command as part of the
|
||||
filename.
|
||||
|
||||
Here is that `date` command run on its own:
|
||||
|
||||
```bash
|
||||
$ date +%Y-%m-%d
|
||||
2025-04-02
|
||||
```
|
||||
|
||||
In the above command, that would mean if I were to run it today, I'd get
|
||||
`scrubbed-database-2025-04-02.dump`.
|
||||
|
||||
This approach can be used with any command where you are producing a file that
|
||||
you want to be dated or timestamped.
|
||||
|
||||
Here is another example that incorporates the time as well:
|
||||
|
||||
```bash
|
||||
$ touch $(date +%Y%m%d_%H%M%S)-migration.sql
|
||||
# => 20250402_092442-migration.sql
|
||||
```
|
||||
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`.
|
||||
26
unix/exclude-a-specific-file-from-fd-results.md
Normal file
26
unix/exclude-a-specific-file-from-fd-results.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Exclude A Specific File From fd Results
|
||||
|
||||
I recent wrote a
|
||||
[`cat-to-markdown`](https://github.com/jbranchaud/dotfiles/blob/my-dotfiles/bin/cat-to-markdown)
|
||||
script that can be piped a list of files from the output of another command to
|
||||
do its thing. I then used [`fd`](https://github.com/sharkdp/fd) to list a
|
||||
specific set of files by extension that could be piped to this command.
|
||||
|
||||
```bash
|
||||
fd -e js | cat-to-markdown | pbcopy
|
||||
```
|
||||
|
||||
This worked, but I quickly realized that one of the JavaScript files included in
|
||||
that listing was massive and didn't need to be included.
|
||||
|
||||
To exclude it from the list I can use the `-E` flag and then name the file like
|
||||
so:
|
||||
|
||||
```bash
|
||||
fd -e js -E super-large-file.js | cat-to-markdown | pbcopy
|
||||
```
|
||||
|
||||
I believe this can be an exact match file path or even a pattern that matches
|
||||
multiple files.
|
||||
|
||||
See `man fd` for more details.
|
||||
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.
|
||||
60
unix/have-script-shellcheck-itself-when-executing.md
Normal file
60
unix/have-script-shellcheck-itself-when-executing.md
Normal file
@@ -0,0 +1,60 @@
|
||||
# Have Script ShellCheck Itself When Executing
|
||||
|
||||
The [ShellCheck](https://www.shellcheck.net/) utility can be run against bash
|
||||
scripts to check if there are any warnings or errors we should fix. It works
|
||||
great as long as we remember to run it.
|
||||
|
||||
I wondered if I could make it easier on myself by not having to remember to run
|
||||
it. What if my bash script were to `shellcheck` itself?
|
||||
|
||||
Here is an example script where at the beginning it looks for and runs the
|
||||
`shellcheck` utility against `$0` (the path of the script). This is kind of
|
||||
meta. As the script is executing, it has an external program run against the
|
||||
entire contents of itself. If there are any `shellcheck` issues, they get
|
||||
displayed and the program exits early.
|
||||
|
||||
```bash
|
||||
#!/bin/bash
|
||||
|
||||
# Exit immediately if any command fails
|
||||
set -e
|
||||
|
||||
# Self-validation using ShellCheck
|
||||
if command -v shellcheck &> /dev/null; then
|
||||
echo "Validating script with ShellCheck..."
|
||||
|
||||
# $0 refers to the script itself
|
||||
if ! shellcheck "$0"; then
|
||||
echo "ShellCheck found issues in the script. Exiting."
|
||||
exit 1
|
||||
fi
|
||||
echo "Script validation passed."
|
||||
else
|
||||
echo "Warning: ShellCheck not found. Skipping validation."
|
||||
fi
|
||||
|
||||
echo "Script execution continuing..."
|
||||
|
||||
# shellcheck warning here
|
||||
read -p "Continue with current operation? (yes/no): " CONTINUE_WITH_EXISTING
|
||||
if [[ ! "$CONTINUE_WITH_EXISTING" =~ ^[Yy][Ee][Ss]$ ]]; then
|
||||
echo "Operation cancelled."
|
||||
exit 1
|
||||
fi
|
||||
```
|
||||
|
||||
This last bit of the script with the `read` command will trigger a warning from
|
||||
`shellcheck`.
|
||||
|
||||
```bash
|
||||
$ ./check.sh
|
||||
Validating script with ShellCheck...
|
||||
|
||||
In ./check.sh line 23:
|
||||
read -p "Continue with current operation? (yes/no): " CONTINUE_WITH_EXISTING
|
||||
^--^ SC2162 (info): read without -r will mangle backslashes.
|
||||
|
||||
For more information:
|
||||
https://www.shellcheck.net/wiki/SC2162 -- read without -r will mangle backs...
|
||||
ShellCheck found issues in the script. Exiting.
|
||||
```
|
||||
22
unix/interpret-cron-schedule-from-the-cli.md
Normal file
22
unix/interpret-cron-schedule-from-the-cli.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Interpret Cron Schedule From The CLI
|
||||
|
||||
I encounter cron task schedules infrequently enough that I don't remember how
|
||||
to reliably interpret them at a glance. So when I'm looking over some code and
|
||||
I see something like `12 2,16 * * *`, I need to translate it to a
|
||||
human-readable format.
|
||||
|
||||
I've typically opened up a browser tab to
|
||||
[crontab.guru](https://crontab.guru/#12_2,16_*_*_*) for this. However, I did
|
||||
just learn about a tool for getting the same information from the CLI.
|
||||
|
||||
With the [`hcron`](https://github.com/lnquy/cron) binary, I can pass in a
|
||||
single cron schedule and get the same details right from my terminal.
|
||||
|
||||
```bash
|
||||
$ hcron '12 2,16 * * *'
|
||||
12 2,16 * * *: At 02:12 AM and 04:12 PM
|
||||
```
|
||||
|
||||
I decided to clone this repo, build the binary from source (it is Go), and then
|
||||
place the `hcron` binary in my `~/bin` directory which is on my path. Once I
|
||||
did that, I could start using the command like above.
|
||||
26
unix/make-neovim-the-default-way-to-view-man-pages.md
Normal file
26
unix/make-neovim-the-default-way-to-view-man-pages.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Make Neovim The Default Way To View Man Pages
|
||||
|
||||
I was reading the help page for `:Man` which is the built-in plugin to Neovim
|
||||
for viewing man pages within a Neovim session. In it, they mentioned that the
|
||||
`MANPAGER` can be set to use Neovim instead of the default man page viewer.
|
||||
|
||||
This can be done by setting `MANPAGER` like so:
|
||||
|
||||
```bash
|
||||
$ export MANPAGER='nvim +Man!'
|
||||
```
|
||||
|
||||
After setting this, you can run something like `man git-restore` which will
|
||||
open the man page for that command in a Neovim session using the Man page
|
||||
plugin which can do things like follow links to other man pages (`K` or
|
||||
`Ctrl=]`), quit by hitting `q`, as well as all the motions and search behavior
|
||||
of Vim.
|
||||
|
||||
For long-term use, this can be set in your shell config, e.g. `~/.zshrc`. For
|
||||
one-off use, you can include it as an env var for a single call to `man`:
|
||||
|
||||
```bash
|
||||
MANPAGER='nvim +Man!' man git-restore
|
||||
```
|
||||
|
||||
See `:h :Man` within a Neovim session for more details.
|
||||
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`.
|
||||
33
unix/transform-text-to-lowercase.md
Normal file
33
unix/transform-text-to-lowercase.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Transform Text To Lowercase
|
||||
|
||||
I was reading through [`setup.sh` in
|
||||
dkarter/dotfiles](https://github.com/dkarter/dotfiles/blob/master/setup.sh#L7-L9)
|
||||
and noticed this function for converting a given bit of text to all lowercase
|
||||
letters.
|
||||
|
||||
```bash
|
||||
lowercase() {
|
||||
echo "$1" | sed "y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/"
|
||||
}
|
||||
```
|
||||
|
||||
It's an interesting use of `sed`, but it made me wonder if `tr` was a better
|
||||
tool for this job. I looked into it and `tr` is better suited to the task, more
|
||||
expressive, and also compatible across Mac and Linux.
|
||||
|
||||
Here is what it looks like with `tr`:
|
||||
|
||||
```bash
|
||||
lowercase() {
|
||||
echo "$1" | tr '[:upper:]' '[:lower:]'
|
||||
}
|
||||
```
|
||||
|
||||
This has the added benefit of working across all kinds of UTF-8 characters.
|
||||
|
||||
```bash
|
||||
$ echo "ΑΛΦΑΒΗΤΟ ΕΛΛΑΔΑ" | tr '[:upper:]' '[:lower:]'
|
||||
αλφαβητο ελλαδα
|
||||
```
|
||||
|
||||
See `man tr` for more details.
|
||||
30
vim/bypass-on-save-tooling-when-writing-file.md
Normal file
30
vim/bypass-on-save-tooling-when-writing-file.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Bypass On-Save Tooling When Writing File
|
||||
|
||||
Every once in a while I run into an issue where my code formatters or linters
|
||||
are misconfigured for a project. I try to save a file and it applies formatting
|
||||
that I don't want. Or in an extreme case, the error ouput of the tool is what
|
||||
overwrites the file.
|
||||
|
||||
I need to troubleshoot my dev tooling eventually, but I don't want to get
|
||||
sidetracked at the moment. I just want to save the file. What can I do?
|
||||
|
||||
Tools like linters and code formatters are typically hooked up to Vim via
|
||||
autocommands on certain actions like `FileWrite*` or `BufWrite*`. We can
|
||||
execute a Vim command like writing a file (`w`) while disregarding autocommands
|
||||
like so:
|
||||
|
||||
```vim
|
||||
:noautocmd w
|
||||
```
|
||||
|
||||
or, write and quit:
|
||||
|
||||
```vim
|
||||
:noautocmd wq
|
||||
```
|
||||
|
||||
This disables all autocommands for this one command. The file gets saved and
|
||||
the misconfigured formatters and linters don't clobber the changes you
|
||||
intended.
|
||||
|
||||
See `:h noautocmd` for more details.
|
||||
18
vim/reword-a-commit-message-with-fugitive.md
Normal file
18
vim/reword-a-commit-message-with-fugitive.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Reword A Commit Message With Fugitive
|
||||
|
||||
When you have the fugitive summary buffer (`:Gedit :`) open and there are
|
||||
unpushed commits, you'll see them listed below the working tree and staging area
|
||||
details. If you notice an issue with the wording of any of those commits, you
|
||||
can initiate an interactive rebase to reword the commit from that window.
|
||||
|
||||
Navigate the cursor over that commit and then hit `rw` (for _reword_).
|
||||
|
||||
This will split open an interactive rebase buffer with `reword <SHA>`. Save that
|
||||
buffer and the commit message will be opened into a buffer where it can be
|
||||
amended, just like if you were to amend a commit with an interactive rebase from
|
||||
the CLI.
|
||||
|
||||
The `rw` binding can be used in any fugitive view where commits are listed. For
|
||||
instance run `:Git log`, navigate to any commit, and then hit `rw`.
|
||||
|
||||
See `:h fugitive_r` for details about all the rebase mappings.
|
||||
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.
|
||||
23
workflow/view-a-nicely-formatted-csv-in-terminal.md
Normal file
23
workflow/view-a-nicely-formatted-csv-in-terminal.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# View Nicely-Formatted CSV In Terminal
|
||||
|
||||
I'd just written and run a script to generate a CSV of data requested by a
|
||||
stakeholder. Before sending it over to them, I wanted to briefly browse through
|
||||
it to make sure the data passed a spot-check and that the way I structured
|
||||
things looked reasonable.
|
||||
|
||||
There are a dozen ways I can view a CSV file on my machine, but I wasn't
|
||||
interested in opening another program or navigating to the file in Finder.app.
|
||||
|
||||
I had generated the file in the terminal and I wanted to view it there.
|
||||
|
||||
The Rust-built [`csvlens`](https://github.com/YS-L/csvlens) CLI is just what I
|
||||
was looking for.
|
||||
|
||||
```bash
|
||||
$ csvlens data_report_20250627.csv
|
||||
```
|
||||
|
||||
This shows the data spaced out with columns and rows and has a set of
|
||||
keybindings that can be browsed with `?`.
|
||||
|
||||
I got this tool from `brew install csvlens`.
|
||||
Reference in New Issue
Block a user