mirror of
https://github.com/jbranchaud/til
synced 2026-01-15 04:58:02 +00:00
Compare commits
64 Commits
b1a41f72b6
...
e3490328df
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e3490328df | ||
|
|
eb3369d296 | ||
|
|
6f47e2f057 | ||
|
|
409201611f | ||
|
|
77cc07a908 | ||
|
|
633c1fa0a5 | ||
|
|
96c394c198 | ||
|
|
0251157dc4 | ||
|
|
97c8701a5a | ||
|
|
1fd64e478a | ||
|
|
8ea123369b | ||
|
|
43c6e08b34 | ||
|
|
61fc021f52 | ||
|
|
46ad33df7e | ||
|
|
2028f6cb09 | ||
|
|
1f039a8958 | ||
|
|
c6eefeac98 | ||
|
|
31a0224fb7 | ||
|
|
aa71ff5f8b | ||
|
|
48278c4908 | ||
|
|
8b3ef4872c | ||
|
|
c2184a5ecf | ||
|
|
e2a8e815e9 | ||
|
|
c61ddcb326 | ||
|
|
7632664200 | ||
|
|
872a1d2a00 | ||
|
|
d52a126767 | ||
|
|
d9080cc583 | ||
|
|
654c65c8f6 | ||
|
|
138cab4fdc | ||
|
|
5592d4266d | ||
|
|
daf448c1a5 | ||
|
|
aaddc35fcd | ||
|
|
b575534d4e | ||
|
|
ae3ecbf72c | ||
|
|
1cf67b8f1a | ||
|
|
f9c0a566eb | ||
|
|
527038ca23 | ||
|
|
b972673008 | ||
|
|
cc31aae25a | ||
|
|
26f30c3225 | ||
|
|
e14da2f207 | ||
|
|
b7d4a62ecb | ||
|
|
1ad41b9776 | ||
|
|
11716a8fb5 | ||
|
|
5e19d53382 | ||
|
|
c8aa6ee506 | ||
|
|
9c0c9222f9 | ||
|
|
855251e478 | ||
|
|
4e5ba0ce4c | ||
|
|
63a92cbc29 | ||
|
|
8438025005 | ||
|
|
a3be570a32 | ||
|
|
464a2af6db | ||
|
|
8801f39df0 | ||
|
|
aeb55efc3c | ||
|
|
a92af09fea | ||
|
|
43e6433fd6 | ||
|
|
88e675b9a3 | ||
|
|
f5286c1f41 | ||
|
|
8787e43458 | ||
|
|
f658a31435 | ||
|
|
db00ec69c2 | ||
|
|
b4080eb9ee |
66
README.md
66
README.md
@@ -10,7 +10,11 @@ pairing with smart people at Hashrocket.
|
||||
|
||||
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
|
||||
|
||||
_1534 TILs and counting..._
|
||||
_1589 TILs and counting..._
|
||||
|
||||
See some of the other learning resources I work on:
|
||||
- [Ruby Operator Lookup](https://www.visualmode.dev/ruby-operators)
|
||||
- [Vim Un-Alphabet](https://www.youtube.com/playlist?list=PL46-cKSxMYYCMpzXo6p0Cof8hJInYgohU)
|
||||
|
||||
---
|
||||
|
||||
@@ -195,12 +199,15 @@ _1534 TILs and counting..._
|
||||
- [Check The Status of All Services](devops/check-the-status-of-all-services.md)
|
||||
- [Check The Syntax Of nginx Files](devops/check-the-syntax-of-nginx-files.md)
|
||||
- [Connect To An RDS PostgreSQL Database](devops/connect-to-an-rds-postgresql-database.md)
|
||||
- [Default Rails Deploy Script On Hatchbox](devops/default-rails-deploy-script-on-hatchbox.md)
|
||||
- [Determine The IP Address Of A Domain](devops/determine-the-ip-address-of-a-domain.md)
|
||||
- [Hatchbox Exports Env Vars With asdf](devops/hatchbox-exports-env-vars-with-asdf.md)
|
||||
- [Path Of The Packets](devops/path-of-the-packets.md)
|
||||
- [Push Non-master Branch To Heroku](devops/push-non-master-branch-to-heroku.md)
|
||||
- [Reload The nginx Configuration](devops/reload-the-nginx-configuration.md)
|
||||
- [Resolve The Public IP Of A URL](devops/resolve-the-public-ip-of-a-url.md)
|
||||
- [Running Out Of inode Space](devops/running-out-of-inode-space.md)
|
||||
- [Set Up Domain For Hatchbox Rails App](devops/set-up-domain-for-hatchbox-rails-app.md)
|
||||
- [SSH Into A Docker Container](devops/ssh-into-a-docker-container.md)
|
||||
- [SSL Certificates Can Cover Multiple Domains](devops/ssl-certificates-can-cover-multiple-domains.md)
|
||||
- [Wipe A Heroku Postgres Database](devops/wipe-a-heroku-postgres-database.md)
|
||||
@@ -283,6 +290,7 @@ _1534 TILs and counting..._
|
||||
- [Add Only Tracked Files From A Directory](git/add-only-tracked-files-from-a-directory.md)
|
||||
- [Amend Author Of Previous Commit](git/amend-author-of-previous-commit.md)
|
||||
- [Auto-Squash Those Fixup Commits](git/auto-squash-those-fixup-commits.md)
|
||||
- [Better Diffs With Delta](git/better-diffs-with-delta.md)
|
||||
- [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)
|
||||
@@ -299,6 +307,7 @@ _1534 TILs and counting..._
|
||||
- [Configuring The Pager](git/configuring-the-pager.md)
|
||||
- [Copy A File From Another Branch](git/copy-a-file-from-another-branch.md)
|
||||
- [Count All Files Of Specific Type Tracked By Git](git/count-all-files-of-specific-type-tracked-by-git.md)
|
||||
- [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 The Hash Id For A Blob](git/determine-the-hash-id-for-a-blob.md)
|
||||
@@ -311,12 +320,14 @@ _1534 TILs and counting..._
|
||||
- [Find And Remove Files That Match A Name](git/find-and-remove-files-that-match-a-name.md)
|
||||
- [Find The Date That A File Was Added To The Repo](git/find-the-date-that-a-file-was-added-to-the-repo.md)
|
||||
- [Find The Initial Commit](git/find-the-initial-commit.md)
|
||||
- [Fix Whitespace Errors Throughout Branch Commits](git/fix-whitespace-errors-throughout-branch-commits.md)
|
||||
- [Get Latest Commit Timestamp For A File](git/get-latest-commit-timestamp-for-a-file.md)
|
||||
- [Get The Name Of The Current Branch](git/get-the-name-of-the-current-branch.md)
|
||||
- [Get The Short Version Of The Latest Commit](git/get-the-short-version-of-the-latest-commit.md)
|
||||
- [Grab A Single File From A Stash](git/grab-a-single-file-from-a-stash.md)
|
||||
- [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)
|
||||
- [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)
|
||||
@@ -327,6 +338,7 @@ _1534 TILs and counting..._
|
||||
- [Interactively Unstage Changes](git/interactively-unstage-changes.md)
|
||||
- [Keep File Locally With `git rm`](git/keep-file-locally-with-git-rm.md)
|
||||
- [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 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)
|
||||
@@ -344,6 +356,7 @@ _1534 TILs and counting..._
|
||||
- [Quicker Commit Fixes With The Fixup Flag](git/quicker-commit-fixes-with-the-fixup-flag.md)
|
||||
- [Rebase Commits With An Arbitrary Command](git/rebase-commits-with-an-arbitrary-command.md)
|
||||
- [Reference A Commit Via Commit Message Pattern Matching](git/reference-a-commit-via-commit-message-pattern-matching.md)
|
||||
- [Reference Commits Earlier Than Reflog Remembers](git/reference-commits-earlier-than-reflog-remembers.md)
|
||||
- [Remove Untracked Files From A Directory](git/remove-untracked-files-from-a-directory.md)
|
||||
- [Rename A Remote](git/rename-a-remote.md)
|
||||
- [Renaming A Branch](git/renaming-a-branch.md)
|
||||
@@ -380,6 +393,7 @@ _1534 TILs and counting..._
|
||||
- [Untrack A Directory Of Files Without Deleting](git/untrack-a-directory-of-files-without-deleting.md)
|
||||
- [Untrack A File Without Deleting It](git/untrack-a-file-without-deleting-it.md)
|
||||
- [Update The URL Of A Remote](git/update-the-url-of-a-remote.md)
|
||||
- [Use External Diff Tool Like Difftastic](git/use-external-diff-tool-like-difftastic.md)
|
||||
- [Using Commands With A Relative Date Format](git/using-commands-with-a-relative-date-format.md)
|
||||
- [Verbose Commit Message](git/verbose-commit-message.md)
|
||||
- [Viewing A File On Another Branch](git/viewing-a-file-on-another-branch.md)
|
||||
@@ -394,22 +408,36 @@ _1534 TILs and counting..._
|
||||
- [Disable A Workflow With The gh CLI](github-actions/disable-a-workflow-with-the-gh-cli.md)
|
||||
- [Reference An Encrypted Secret In An Action](github-actions/reference-an-encrypted-secret-in-an-action.md)
|
||||
- [Trigger A Workflow Via An API Call](github-actions/trigger-a-workflow-via-an-api-call.md)
|
||||
- [Use Labels To Block PR Merge](github-actions/use-labels-to-block-pr-merge.md)
|
||||
|
||||
### Go
|
||||
|
||||
- [Access Go Docs Offline](go/access-go-docs-offline.md)
|
||||
- [Add A Method To A Struct](go/add-a-method-to-a-struct.md)
|
||||
- [Basic Delve Debugging Session](go/basic-delve-debugging-session.md)
|
||||
- [Build For A Specific OS And Architecture](go/build-for-a-specific-os-and-architecture.md)
|
||||
- [Check If Cobra Flag Was Set](go/check-if-cobra-flag-was-set.md)
|
||||
- [Combine Two Slices](go/combine-two-slices.md)
|
||||
- [Configure Max String Print Length For Delve](go/configure-max-string-print-length-for-delve.md)
|
||||
- [Connect To A SQLite Database](go/connect-to-a-sqlite-database.md)
|
||||
- [Create A Slice From An Array](go/create-a-slice-from-an-array.md)
|
||||
- [Detect If Stdin Comes From A Redirect](go/detect-if-stdin-comes-from-a-redirect.md)
|
||||
- [Deterministically Seed A Random Number Generator](go/deterministically-seed-a-random-number-generator.md)
|
||||
- [Difference Between Slice And Pointer To Slice](go/difference-between-slice-and-pointer-to-slice.md)
|
||||
- [Do Something N Times](go/do-something-n-times.md)
|
||||
- [Find Executables Installed By Go](go/find-executables-installed-by-go.md)
|
||||
- [Format Date And Time With Time Constants](go/format-date-and-time-with-time-constants.md)
|
||||
- [Not So Random](go/not-so-random.md)
|
||||
- [Parse A String Into Individual Fields](go/parse-a-string-into-individual-fields.md)
|
||||
- [Parse Flags From CLI Arguments](go/parse-flags-from-cli-arguments.md)
|
||||
- [Pass A Struct To A Function](go/pass-a-struct-to-a-function.md)
|
||||
- [Produce The Zero Value Of A Generic Type](go/produce-the-zero-value-of-a-generic-type.md)
|
||||
- [Redirect File To Stdin During Delve Debug](go/redirect-file-to-stdin-during-delve-debug.md)
|
||||
- [Replace The Current Process With An External Command](go/replace-the-current-process-with-an-external-command.md)
|
||||
- [Sleep For A Duration](go/sleep-for-a-duration.md)
|
||||
- [Sort Slice In Ascending Or Descending Order](go/sort-slice-in-ascending-or-descending-order.md)
|
||||
- [Upgrading From An Older Version On Mac](go/upgrading-from-an-older-version-on-mac.md)
|
||||
- [Write A Custom Scan Function For File IO](go/write-a-custom-scan-function-for-file-io.md)
|
||||
|
||||
### GROQ
|
||||
|
||||
@@ -433,6 +461,7 @@ _1534 TILs and counting..._
|
||||
- [Adding Alt Text To An Image](html/adding-alt-text-to-an-image.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)
|
||||
- [Make Elements Non-Interactive With Inert](html/make-elements-non-interactive-with-inert.md)
|
||||
- [Prevent Search Engines From Indexing A Page](html/prevent-search-engines-from-indexing-a-page.md)
|
||||
- [Render Text As Superscript](html/render-text-as-superscript.md)
|
||||
@@ -641,6 +670,9 @@ _1534 TILs and counting..._
|
||||
- [Run AppleScript Commands Inline In The Terminal](mac/run-applescript-commands-inline-in-the-terminal.md)
|
||||
- [Set A Window To Its Default Zoom Level](mac/set-a-window-to-its-default-zoom-level.md)
|
||||
- [Specify App When Opening From Command Line](mac/specify-app-when-opening-from-command-line.md)
|
||||
- [Start Amphetamine Session With AppleScript](mac/start-amphetamine-session-with-applescript.md)
|
||||
- [Uninstall LogiTech G Hub From Mac](mac/uninstall-logitech-g-hub-from-mac.md)
|
||||
- [Use A Different Font With iTerm2](mac/use-a-different-font-with-iterm2.md)
|
||||
- [Use Default Screenshot Shortcuts With CleanShot X](mac/use-default-screenshot-shortcuts-with-cleanshot-x.md)
|
||||
- [View All Windows Of The Current App](mac/view-all-windows-of-the-current-app.md)
|
||||
- [Write System Clipboard To A File](mac/write-system-clipboard-to-a-file.md)
|
||||
@@ -752,6 +784,7 @@ _1534 TILs and counting..._
|
||||
- [Convert A String To A Timestamp](postgres/convert-a-string-to-a-timestamp.md)
|
||||
- [Count How Many Records There Are Of Each Type](postgres/count-how-many-records-there-are-of-each-type.md)
|
||||
- [Count Records By Type](postgres/count-records-by-type.md)
|
||||
- [Count The Number Of Items In An Array](postgres/count-the-number-of-items-in-an-array.md)
|
||||
- [Count The Number Of Trues In An Aggregate Query](postgres/count-the-number-of-trues-in-an-aggregate-query.md)
|
||||
- [Create A Cluster In A Specific Data Directory](postgres/create-a-cluster-in-a-specific-data-directory.md)
|
||||
- [Create A Composite Primary Key](postgres/create-a-composite-primary-key.md)
|
||||
@@ -869,12 +902,14 @@ _1534 TILs and counting..._
|
||||
- [Timestamp Functions](postgres/timestamp-functions.md)
|
||||
- [Toggling The Pager In PSQL](postgres/toggling-the-pager-in-psql.md)
|
||||
- [Track psql History Separately Per Database](postgres/track-psql-history-separately-per-database.md)
|
||||
- [Trim Leading And Trailing Space From String](postgres/trim-leading-and-trailing-space-from-string.md)
|
||||
- [Truncate All Rows](postgres/truncate-all-rows.md)
|
||||
- [Truncate Tables With Dependents](postgres/truncate-tables-with-dependents.md)
|
||||
- [Turning Timing On](postgres/turn-timing-on.md)
|
||||
- [Two Ways To Compute Factorial](postgres/two-ways-to-compute-factorial.md)
|
||||
- [Two Ways To Escape A Quote In A String](postgres/two-ways-to-escape-a-quote-in-a-string.md)
|
||||
- [Types By Category](postgres/types-by-category.md)
|
||||
- [Unable To Infer Data Type In Production](postgres/unable-to-infer-data-type-in-production.md)
|
||||
- [Union All Rows Including Duplicates](postgres/union-all-rows-including-duplicates.md)
|
||||
- [Use A psqlrc File For Common Settings](postgres/use-a-psqlrc-file-for-common-settings.md)
|
||||
- [Use A Trigger To Mirror Inserts To Another Table](postgres/use-a-trigger-to-mirror-inserts-to-another-table.md)
|
||||
@@ -915,6 +950,7 @@ _1534 TILs and counting..._
|
||||
- [Add A Check Constraint To A Table](rails/add-a-check-constraint-to-a-table.md)
|
||||
- [Add A Database Index If It Does Not Already Exist](rails/add-a-database-index-if-it-does-not-already-exist.md)
|
||||
- [Add A Foreign Key Reference To A Table](rails/add-a-foreign-key-reference-to-a-table.md)
|
||||
- [Add A Generated Column To A PostgreSQL Table](rails/add-a-generated-column-to-a-postgresql-table.md)
|
||||
- [Add A Reference Column With An Index](rails/add-a-reference-column-with-an-index.md)
|
||||
- [Add ActiveRecord Error Not Tied To Any Attribute](rails/add-activerecord-error-not-tied-to-any-attribute.md)
|
||||
- [Add React With Webpacker To A New Rails App](rails/add-react-with-webpacker-to-a-new-rails-app.md)
|
||||
@@ -927,6 +963,7 @@ _1534 TILs and counting..._
|
||||
- [All or Nothing Database Transactions](rails/all-or-nothing-database-transactions.md)
|
||||
- [Alphabetize Schema Columns To Keep Them Consistent](rails/alphabetize-schema-columns-to-keep-them-consistent.md)
|
||||
- [Alter The Rails Setup Script](rails/alter-the-rails-setup-script.md)
|
||||
- [Apply Basic HTML Formatting To Block Of Text](rails/apply-basic-html-formatting-to-block-of-text.md)
|
||||
- [Assert Two Arrays Have The Same Items With RSpec](rails/assert-two-arrays-have-the-same-items-with-rspec.md)
|
||||
- [Attach A File With Capybara](rails/attach-a-file-with-capybara.md)
|
||||
- [Attribute Getter without the Recursion](rails/attribute-getter-without-the-recursion.md)
|
||||
@@ -958,6 +995,7 @@ _1534 TILs and counting..._
|
||||
- [Define The Root Path For The App](rails/define-the-root-path-for-the-app.md)
|
||||
- [Delete Paranoid Records](rails/delete-paranoid-records.md)
|
||||
- [Demodulize A Class Name](rails/demodulize-a-class-name.md)
|
||||
- [Determine The Configured Primary Key Type](rails/determine-the-configured-primary-key-type.md)
|
||||
- [Different Ways To Add A Foreign Key Reference](rails/different-ways-to-add-a-foreign-key-reference.md)
|
||||
- [Disambiguate Where In A Joined Relation](rails/disambiguate-where-in-a-joined-relation.md)
|
||||
- [Empty find_by Returns First Record](rails/empty-find-by-returns-first-record.md)
|
||||
@@ -983,6 +1021,7 @@ _1534 TILs and counting..._
|
||||
- [Hash Slicing](rails/hash-slicing.md)
|
||||
- [Ignore Poltergeist JavaScript Errors](rails/ignore-poltergeist-javascript-errors.md)
|
||||
- [Include Devise Helpers In Your Controller Tests](rails/include-devise-helpers-in-your-controller-tests.md)
|
||||
- [Inspect Configuration Of Database Connection](rails/inspect-configuration-of-database-connection.md)
|
||||
- [Inspect Previous Changes To ActiveRecord Object](rails/inspect-previous-changes-to-activerecord-object.md)
|
||||
- [Link To The Current Page With Query Params](rails/link-to-the-current-page-with-query-params.md)
|
||||
- [List All Installable Rails Versions](rails/list-all-installable-rails-versions.md)
|
||||
@@ -1002,6 +1041,7 @@ _1534 TILs and counting..._
|
||||
- [Migrating Up Down Up](rails/migrating-up-down-up.md)
|
||||
- [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)
|
||||
- [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)
|
||||
@@ -1014,6 +1054,7 @@ _1534 TILs and counting..._
|
||||
- [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)
|
||||
- [Rebuild Tailwind Bundle For Dev Server](rails/rebuild-tailwind-bundle-for-dev-server.md)
|
||||
- [Remove A Database Column From A Table](rails/remove-a-database-column-from-a-table.md)
|
||||
- [Remove The Default Value On A Column](rails/remove-the-default-value-on-a-column.md)
|
||||
- [Render An Alternative ActionMailer Template](rails/render-an-alternative-action-mailer-template.md)
|
||||
@@ -1029,6 +1070,7 @@ _1534 TILs and counting..._
|
||||
- [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 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)
|
||||
- [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)
|
||||
@@ -1039,6 +1081,7 @@ _1534 TILs and counting..._
|
||||
- [Set DateTime To Include Time Zone In Migrations](rails/set-datetime-to-include-time-zone-in-migrations.md)
|
||||
- [Set Default As SQL Function In Migration](rails/set-default-as-sql-function-in-migration.md)
|
||||
- [Set default_url_options For Entire Application](rails/set-default-url-options-for-entire-application.md)
|
||||
- [Set Meta Tags In ERB Views](rails/set-meta-tags-in-erb-views.md)
|
||||
- [Set Schema Search Path](rails/set-schema-search-path.md)
|
||||
- [Set Statement Timeout For All Postgres Connections](rails/set-statement-timeout-for-all-postgres-connections.md)
|
||||
- [Set The Default Development Port](rails/set-the-default-development-port.md)
|
||||
@@ -1206,6 +1249,7 @@ _1534 TILs and counting..._
|
||||
- [Check If A URL Resolves To 200](ruby/check-if-a-url-resolves-to-200.md)
|
||||
- [Check If An Object Includes A Module](ruby/check-if-an-object-includes-a-module.md)
|
||||
- [Check Return Status Of Running A Shell Command](ruby/check-return-status-of-running-a-shell-command.md)
|
||||
- [Clamp To An Endless Range](ruby/clamp-to-an-endless-range.md)
|
||||
- [Click On Text With Capybara](ruby/click-on-text-with-capybara.md)
|
||||
- [Colorful Output With MiniTest](ruby/colorful-output-with-minitest.md)
|
||||
- [Comparing Class Hierarchy Relationships](ruby/comparing-class-hierarchy-relationships.md)
|
||||
@@ -1237,6 +1281,7 @@ _1534 TILs and counting..._
|
||||
- [Exit A Process With An Error Message](ruby/exit-a-process-with-an-error-message.md)
|
||||
- [Expect A Method To Be Called And Actually Call It](ruby/expect-a-method-to-be-called-and-actually-call-it.md)
|
||||
- [Extract A Column Of Data From A CSV File](ruby/extract-a-column-of-data-from-a-csv-file.md)
|
||||
- [Extract Capture Group Matches With String Slices](ruby/extract-capture-group-matches-with-string-slices.md)
|
||||
- [FactoryGirl Sequences](ruby/factory-girl-sequences.md)
|
||||
- [Fail](ruby/fail.md)
|
||||
- [Fetch Warns About Superseding Block Argument](ruby/fetch-warns-about-superseding-block-argument.md)
|
||||
@@ -1255,6 +1300,7 @@ _1534 TILs and counting..._
|
||||
- [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 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)
|
||||
- [Jump Out Of A Nested Context With Throw/Catch](ruby/jump-out-of-a-nested-context-with-throw-catch.md)
|
||||
@@ -1287,6 +1333,7 @@ _1534 TILs and counting..._
|
||||
- [Question Mark Operator](ruby/question-mark-operator.md)
|
||||
- [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)
|
||||
- [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)
|
||||
@@ -1354,6 +1401,7 @@ _1534 TILs and counting..._
|
||||
### SQLite
|
||||
|
||||
- [Display Results In Readable Column Format](sqlite/display-results-in-readable-column-format.md)
|
||||
- [Explore The Database Schema](sqlite/explore-the-database-schema.md)
|
||||
|
||||
### Streaming
|
||||
|
||||
@@ -1452,6 +1500,7 @@ _1534 TILs and counting..._
|
||||
- [Count The Lines In A CSV Where A Column Is Empty](unix/count-the-lines-in-a-csv-where-a-column-is-empty.md)
|
||||
- [Count The Number Of Matches In A Grep](unix/count-the-number-of-matches-in-a-grep.md)
|
||||
- [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 Sequence Of Values With A Step](unix/create-a-sequence-of-values-with-a-step.md)
|
||||
- [Curl With Cookies](unix/curl-with-cookies.md)
|
||||
@@ -1488,6 +1537,7 @@ _1534 TILs and counting..._
|
||||
- [Get Matching Filenames As Output From Grep](unix/get-matching-filenames-as-output-from-grep.md)
|
||||
- [Get The SHA256 Hash For A File](unix/get-the-sha256-hash-for-a-file.md)
|
||||
- [Get The Unix Timestamp](unix/get-the-unix-timestamp.md)
|
||||
- [Get Word Count For All Files In Git Repo](unix/get-word-count-for-all-files-in-git-repo.md)
|
||||
- [Global Substitution On The Previous Command](unix/global-substitution-on-the-previous-command.md)
|
||||
- [Globbing For All Directories In Zsh](unix/globbing-for-all-directories-in-zsh.md)
|
||||
- [Globbing For Filenames In Zsh](unix/globbing-for-filenames-in-zsh.md)
|
||||
@@ -1506,6 +1556,8 @@ _1534 TILs and counting..._
|
||||
- [Killing A Frozen SSH Session](unix/killing-a-frozen-ssh-session.md)
|
||||
- [Last Argument Of The Last Command](unix/last-argument-of-the-last-command.md)
|
||||
- [Less With Style](unix/less-with-style.md)
|
||||
- [Limit Protocols Used In A cURL Command](unix/limit-protocols-used-in-a-curl-command.md)
|
||||
- [List All Fonts On Your Machine](unix/list-all-fonts-on-your-machine.md)
|
||||
- [List All The Enabled ZSH Options](unix/list-all-the-enabled-zsh-options.md)
|
||||
- [List All Users](unix/list-all-users.md)
|
||||
- [List Files In A Single Column](unix/list-files-in-a-single-column.md)
|
||||
@@ -1520,6 +1572,7 @@ _1534 TILs and counting..._
|
||||
- [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)
|
||||
- [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)
|
||||
- [Occupy A Local Port With Netcat](unix/occupy-a-local-port-with-netcat.md)
|
||||
@@ -1547,6 +1600,7 @@ _1534 TILs and counting..._
|
||||
- [Search History](unix/search-history.md)
|
||||
- [Search Man Page Descriptions](unix/search-man-page-descriptions.md)
|
||||
- [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)
|
||||
- [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)
|
||||
@@ -1770,8 +1824,10 @@ _1534 TILs and counting..._
|
||||
- [Add Subtitles To Existing Mux Video Asset](workflow/add-subtitles-to-existing-mux-video-asset.md)
|
||||
- [Access 1Password Credential From CLI](workflow/access-1password-credential-from-cli.md)
|
||||
- [Allow Key-Repeating With Cursor](workflow/allow-key-repeating-with-cursor.md)
|
||||
- [Break Justfile Into Separate Hidden Steps](workflow/break-justfile-into-separate-hidden-steps.md)
|
||||
- [Change Window Name In iTerm](workflow/change-window-name-in-iterm.md)
|
||||
- [Configure Email Redirect With Cloudflare](workflow/configure-email-redirect-with-cloudflare.md)
|
||||
- [Control Media With Drop Keyboard](workflow/control-media-with-drop-keyboard.md)
|
||||
- [Convert An ePub Document To PDF On Mac](workflow/convert-an-epub-document-to-pdf-on-mac.md)
|
||||
- [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)
|
||||
@@ -1782,11 +1838,15 @@ _1534 TILs and counting..._
|
||||
- [Import A Github Project Into CodeSandbox](workflow/import-a-github-project-into-codesandbox.md)
|
||||
- [Interactively Kill A Process With fkill](workflow/interactively-kill-a-process-with-fkill.md)
|
||||
- [Open Slack's Keyboard Shortcuts Reference Panel](workflow/open-slacks-keyboard-shortcuts-reference-panel.md)
|
||||
- [Pop Videos Out As Picture-in-Picture](workflow/pop-videos-out-as-picture-in-picture.md)
|
||||
- [Prune The Excess From node_modules](workflow/prune-the-excess-from-node-modules.md)
|
||||
- [Rotate An Image To Be Oriented Upright](workflow/rotate-an-image-to-be-oriented-upright.md)
|
||||
- [See Overlaps For A Set Of Time Zones](workflow/see-overlaps-for-a-set-of-time-zones.md)
|
||||
- [Send A Message To A Discord Channel](workflow/send-a-message-to-a-discord-channel.md)
|
||||
- [Send A PDF To Your Kindle](workflow/send-a-pdf-to-your-kindle.md)
|
||||
- [Set Recurring Reminders In Slack](workflow/set-recurring-reminders-in-slack.md)
|
||||
- [Show Linting Errors In Zed](workflow/show-linting-errors-in-zed.md)
|
||||
- [Temporarily Hide CleanShot X Capture Previews](workflow/temporarily-hide-cleanshot-x-capture-previews.md)
|
||||
- [Toggle Between Stories In Storybook](workflow/toggle-between-stories-in-storybook.md)
|
||||
- [Update asdf Plugins With Latest Package Versions](workflow/update-asdf-plugins-with-latest-package-versions.md)
|
||||
- [View The PR For The Current GitHub Branch](workflow/view-the-pr-for-the-current-github-branch.md)
|
||||
@@ -1838,11 +1898,11 @@ I shamelessly stole this idea from
|
||||
|
||||
* [Today I Learned by Hashrocket](https://til.hashrocket.com)
|
||||
* [jwworth/til](https://github.com/jwworth/til)
|
||||
* [thoughtbot/til](https://github.com/thoughtbot/til)
|
||||
* [til.simonwillison.net](https://til.simonwillison.net/)
|
||||
|
||||
## License
|
||||
|
||||
© 2015-2022 Josh Branchaud
|
||||
© 2015-2025 Josh Branchaud
|
||||
|
||||
This repository is licensed under the MIT license. See `LICENSE` for
|
||||
details.
|
||||
|
||||
28
devops/default-rails-deploy-script-on-hatchbox.md
Normal file
28
devops/default-rails-deploy-script-on-hatchbox.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Default Rails Deploy Script On Hatchbox
|
||||
|
||||
I deployed a Rails app to [Hatchbox](https://hatchbox.io) recently. When
|
||||
following along in the log during a deploy, I can see most of what is happening
|
||||
as part of the deploy. Though it is too verbose to look through every line. I'd
|
||||
rather see the contents of the deploy script.
|
||||
|
||||
I did quite a bit of digging around while SSH'd into my hatchbox server, but I
|
||||
couldn't find if or where that file might be stored.
|
||||
|
||||
Instead, there is a [_Help Center_
|
||||
article](https://hatchbox.relationkit.io/articles/55-what-is-the-default-rails-deploy-script)
|
||||
where Chris Oliver shares what is in the script.
|
||||
|
||||
```bash
|
||||
bundle install -j $(nproc)
|
||||
yarn install
|
||||
bundle exec rails assets:precompile
|
||||
[[ -n "${CRON}" ]] && bundle exec rails db:migrate
|
||||
```
|
||||
|
||||
It does a parallelized `bundle install`, then a `yarn install` (make sure your
|
||||
project is using `yarn.lock`), Rails asset precompilation, and then if `CRON`
|
||||
is set (Cron role is available by checking _Cron_ under _Server
|
||||
Responsibilities_ for your Hatchbox server), it will run Rails migrations.
|
||||
|
||||
From app settings, the deploy script can be overridden, or pre- and post-deploy
|
||||
steps can be added.
|
||||
44
devops/hatchbox-exports-env-vars-with-asdf.md
Normal file
44
devops/hatchbox-exports-env-vars-with-asdf.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Hatchbox Exports Env Vars With asdf
|
||||
|
||||
When you add env vars through the [Hatchbox](https://hatchbox.io/) UI, they get
|
||||
exported to the environment of the asdf-shimmed processes. This is handled by
|
||||
the [`asdf-vars` plugin](https://github.com/excid3/asdf-vars). That plugin
|
||||
looks for `.asdf-vars` in the current chain of directories.
|
||||
|
||||
I can see there are many `.asdf-vars` files:
|
||||
|
||||
```bash
|
||||
$ find . -name ".asdf-vars" -type f
|
||||
./.asdf-vars
|
||||
./my-app/.asdf-vars
|
||||
./my-app/releases/20250120195106/.asdf-vars
|
||||
./my-app/releases/20250121041054/.asdf-vars
|
||||
```
|
||||
|
||||
And it is the one in my app's directory that contains the env vars that I set
|
||||
in the UI.
|
||||
|
||||
```bash
|
||||
$ cat my-app/.asdf-vars
|
||||
BUNDLE_WITHOUT=development:test
|
||||
DATABASE_URL=postgresql://user_123:123456789012345@10.0.1.1/my_app_db
|
||||
PORT=9000
|
||||
RACK_ENV=production
|
||||
RAILS_ENV=production
|
||||
RAILS_LOG_TO_STDOUT=true
|
||||
RAILS_MASTER_KEY=abc123
|
||||
SECRET_KEY_BASE=abc123efg456
|
||||
```
|
||||
|
||||
When I run a shimmed process like `ruby`, those env vars are loaded into the
|
||||
process's environment.
|
||||
|
||||
```bash
|
||||
$ cd my-app/current
|
||||
$ which ruby
|
||||
/home/deploy/.asdf/shims/ruby
|
||||
$ ruby -e "puts ENV['DATABASE_URL']"
|
||||
postgresql://user_123:123456789012345@10.0.1.1/my_app_db
|
||||
```
|
||||
|
||||
[source](https://www.visualmode.dev/hatchbox-manages-env-vars-with-asdf)
|
||||
24
devops/set-up-domain-for-hatchbox-rails-app.md
Normal file
24
devops/set-up-domain-for-hatchbox-rails-app.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Set Up Domain For Hatchbox Rails App
|
||||
|
||||
When we deploy a Rails app with [Hatchbox](https://hatchbox.io), we are given
|
||||
an internal URL for publicly accessing our app. It is something like
|
||||
`https://123abc.hatchboxapp.com`. That's useful as we are getting things up and
|
||||
running, but eventually we want to point our own domain at the app.
|
||||
|
||||
The first step is to tell Hatchbox what domain we are going to use.
|
||||
|
||||
From our app's _Domain & SSL_ page we can enter a domain into the _Add A
|
||||
Domain_ input. For instance, I have the
|
||||
[visualmode.dev](https://visualmode.dev) domain and I want the
|
||||
[still.visualmode.dev](https://still.visualmode.dev) subdomain pointing at my
|
||||
Rails app. I submit the full name `still.visualmode.dev` and I get an _A
|
||||
Record_ ipv4 address (e.g. `23.12.234.82`).
|
||||
|
||||
The second step is to configure a DNS record with our domain registrar.
|
||||
|
||||
From the DNS settings of our registrar (e.g. Cloudflare) we can add an _A
|
||||
Record_ where we specify the name (e.g. `still`) and then include the ipv4
|
||||
address provided by Hatchbox. We can save this and wait a minute for it to
|
||||
propagate.
|
||||
|
||||
And soon enough we can visit our Rails app at the custom domain.
|
||||
43
git/better-diffs-with-delta.md
Normal file
43
git/better-diffs-with-delta.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Better Diffs With Delta
|
||||
|
||||
A `git diff` from the command line is relatively bare bones. It shows you
|
||||
removed lines and added lines that make up a changeset with the former text in
|
||||
red and the later text in green. All other contextual text is in white. I've
|
||||
found this to be good enough for most of the life of my git usage. I've been
|
||||
missing out though.
|
||||
|
||||
By using [`delta`](https://github.com/dandavison/delta) as the pager and diff
|
||||
filter for `git`, I get a bunch of nice visual improvements.
|
||||
|
||||
- Removals and additions are red and green shaded backgrounds
|
||||
- Syntax highlighting for most languages
|
||||
- Highlight specific part of a line that has changed
|
||||
- Visual spacing and layout is clearer
|
||||
|
||||
To get all of this, all I had to do was install `delta`:
|
||||
|
||||
```bash
|
||||
$ brew install delta
|
||||
```
|
||||
|
||||
And then add `delta` as both the _core_ pager and `diffFilter` in my global git
|
||||
config file:
|
||||
|
||||
```
|
||||
[core]
|
||||
pager = delta
|
||||
[interactive]
|
||||
singleKey = true # unrelated, but nice to have
|
||||
diffFilter = delta --color-only
|
||||
```
|
||||
|
||||
It's also recommended that you use `zdiff3` for your merge conflict style,
|
||||
which I already had:
|
||||
|
||||
```
|
||||
[merge]
|
||||
conflictstyle = zdiff3
|
||||
```
|
||||
|
||||
Once you have ths all configred, try a `git diff` or `git add --patch` and see
|
||||
how much more visual info you get.
|
||||
48
git/count-number-of-commits-on-a-branch.md
Normal file
48
git/count-number-of-commits-on-a-branch.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Count Number Of Commits On A Branch
|
||||
|
||||
The `git rev-list` command will show all commits that fit the given revision
|
||||
criteria. By adding in the `--count` flag, we get a count of the number of
|
||||
commits that would have been displayed. Knowing this, we can get the count of
|
||||
commits for the current branch like so:
|
||||
|
||||
```bash
|
||||
$ git rev-list --count HEAD
|
||||
4
|
||||
```
|
||||
|
||||
This finds and counts commits from `HEAD` (usually the top of the current
|
||||
branch) all the back in reverse chronological order to the beginning of the
|
||||
branch (typically the beginning of the repository). This works exactly as
|
||||
expected for a the `main` branch.
|
||||
|
||||
What about when we are on a feature branch though?
|
||||
|
||||
Let's say we've branched off `main` and made a few commits. And now we want the
|
||||
count.
|
||||
|
||||
```bash
|
||||
$ git rev-list --count HEAD
|
||||
7
|
||||
```
|
||||
|
||||
Unfortunately, that is counting up the commits on the feature branch but it
|
||||
keeps counting all the way back to the beginning of the repo.
|
||||
|
||||
If we want a count of just the commits on the current branch, then we can
|
||||
specify a range: from whatever `main` was when we branched to the `HEAD` of
|
||||
this branch.
|
||||
|
||||
```bash
|
||||
$ git rev-list --count HEAD
|
||||
3
|
||||
```
|
||||
|
||||
This is the same as saying, I want all commits on `HEAD`, but exclude (`^`) the
|
||||
commits on `main`:
|
||||
|
||||
```bash
|
||||
git rev-list --count HEAD ^main
|
||||
3
|
||||
```
|
||||
|
||||
See `man git-rev-list` for more details.
|
||||
39
git/fix-whitespace-errors-throughout-branch-commits.md
Normal file
39
git/fix-whitespace-errors-throughout-branch-commits.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Fix Whitespace Errors Throughout Branch Commits
|
||||
|
||||
Let's say we've been working on some changes to our repository on a branch.
|
||||
We've made several commits. We are close to putting up a PR, but we want to
|
||||
make sure everything is tidied up.
|
||||
|
||||
We run a check and see that there are some whitespace errors that should be
|
||||
fixed.
|
||||
|
||||
```bash
|
||||
$ git diff main --check
|
||||
README.md:1: trailing whitespace.
|
||||
+# git-playground
|
||||
script.sh:9: trailing whitespace.
|
||||
+
|
||||
```
|
||||
|
||||
This post isn't able to show the highlighted whitespace errors, but we can see
|
||||
the warnings above.
|
||||
|
||||
Rather than cluttering things with an additional commit that fixes these errors
|
||||
or manually cleaning up each commit, we can ask `git` to fix it for us.
|
||||
|
||||
```bash
|
||||
$ git rebase --whitespace=fix main
|
||||
```
|
||||
|
||||
That will do a manual rebase of each commit addressing the whitespace errors.
|
||||
|
||||
We can run the error check again and see no output, which means we are good to
|
||||
go.
|
||||
|
||||
```bash
|
||||
$ git diff main --check
|
||||
```
|
||||
|
||||
See the section on `--whitespace` in `man git-apply` for more details.
|
||||
|
||||
[source](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration)
|
||||
30
git/highlight-extra-whitespace-in-diff-output.md
Normal file
30
git/highlight-extra-whitespace-in-diff-output.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# Highlight Extra Whitespace In Diff Output
|
||||
|
||||
When running a `git diff` (or `git add --patch`) I'll sometimes come across
|
||||
lines that don't have any visible changes. This is usually because some
|
||||
whitespace characters were either added (on accident) or removed (often by a
|
||||
autoformatter).
|
||||
|
||||
Depending on the `core.whitespace` config, you'll probably see at least some of
|
||||
the whitespace errors that git provides. By default, git only highlights
|
||||
whitespace errors on added (`new`) lines. However if some extra whitespace was
|
||||
originally committed and is now being removed, it won't be highlighted on the
|
||||
`old` line in the diff.
|
||||
|
||||
We can have git always highlight whitespace errors by setting
|
||||
`wsErrorHighlight` to `all` in the global git config.
|
||||
|
||||
```bash
|
||||
$ git config --global diff.wsErrorHighlight all
|
||||
```
|
||||
|
||||
Which updates the global gitconfig file with the following line:
|
||||
|
||||
```
|
||||
[diff]
|
||||
wsErrorHighlight = all
|
||||
```
|
||||
|
||||
The `all` option is a shorthand for `old,new,context`.
|
||||
|
||||
See `man git-diff` for more details.
|
||||
49
git/list-all-files-added-during-span-of-time.md
Normal file
49
git/list-all-files-added-during-span-of-time.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# List All Files Added During Span Of Time
|
||||
|
||||
I wanted to get an idea of all the TIL posts I wrote during 2024. Every TIL I
|
||||
write is under version control in a [git repo on
|
||||
github](https://github.com/jbranchaud/til). That means git has all the info I
|
||||
need to figure that out.
|
||||
|
||||
The `git diff` command is a good way at this problem. With the
|
||||
`--diff-filter=A` flag I can restrict the results to just files that were
|
||||
_Added_. And with `--name-only` I can cut all the other diff details out and
|
||||
get just filenames.
|
||||
|
||||
But filenames added to which commits? We need to specify a ref range. There is
|
||||
a ton of flexibility in how you define a ref, including [a date specification
|
||||
suffix](https://git-scm.com/docs/gitrevisions#Documentation/gitrevisions.txt-emltrefnamegtltdategtemegemmasteryesterdayememHEAD5minutesagoem)
|
||||
that points to the value of the ref at an earlier point in time.
|
||||
|
||||
So, how about from the beginning of 2024 to the beginning of 2025:
|
||||
|
||||
```
|
||||
HEAD@{2024-01-01}..HEAD@{2025-01-01}
|
||||
```
|
||||
|
||||
Putting that all together, we this command and potentially a big list of files.
|
||||
|
||||
```bash
|
||||
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01}
|
||||
```
|
||||
|
||||
I wanted to restrict the results to just markdown files, so I added a filename
|
||||
pattern.
|
||||
|
||||
```bash
|
||||
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01} -- "*.md"
|
||||
```
|
||||
|
||||
I could even go a step further to see only the files added to a specific
|
||||
directory.
|
||||
|
||||
```bash
|
||||
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01} -- "postgres/*.md"
|
||||
```
|
||||
|
||||
As a final bonus, I can spit out the github URLs for all those files with a bit of `awk`.
|
||||
|
||||
```bash
|
||||
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01} -- "postgres/*.md" |
|
||||
awk '{print "https://github.com/jbranchaud/til/blob/master/" $0}'
|
||||
```
|
||||
34
git/reference-commits-earlier-than-reflog-remembers.md
Normal file
34
git/reference-commits-earlier-than-reflog-remembers.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Reference Commits Earlier Than Reflog Remembers
|
||||
|
||||
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.
|
||||
|
||||
```bash
|
||||
❯ git diff --diff-filter=A --name-only HEAD@{2016-02-06}..HEAD@{2017-02-06} -- "*.md"
|
||||
warning: log for 'HEAD' only goes back to Sun, 20 Dec 2020 00:26:27 -0600
|
||||
warning: log for 'HEAD' only goes back to Sun, 20 Dec 2020 00:26:27 -0600
|
||||
```
|
||||
|
||||
This is because `HEAD@...` is a reference to the `reflog`. The `reflog` is a
|
||||
local-only log of objects and activity in the repository. That date looks
|
||||
suspiciously like the time that I got this specific machine and cloned the
|
||||
repo.
|
||||
|
||||
In order to access this information, I need a different approach of finding
|
||||
references that bound these points in time.
|
||||
|
||||
How about asking `rev-list` for the first commit it can find before the given
|
||||
dates in 2017 and 2016 and then using those.
|
||||
|
||||
```bash
|
||||
❯ git rev-list -1 --before="2017-02-07 00:00" HEAD
|
||||
17db6bc4468616786a8f597a10d252c24183d82e
|
||||
|
||||
❯ git rev-list -1 --before="2016-02-07 00:00" HEAD
|
||||
f1d3d1f796007662ff448d6ba0e3bbf38a2b858d
|
||||
|
||||
❯ git diff --diff-filter=A --name-only f1d3d1f796007662ff448d6ba0e3bbf38a2b858d..17db6bc4468616786a8f597a10d252c24183d82e -- "*.md"
|
||||
|
||||
# git outputs a bunch of files ...
|
||||
```
|
||||
23
git/use-external-diff-tool-like-difftastic.md
Normal file
23
git/use-external-diff-tool-like-difftastic.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# Use External Diff Tool Like Difftastic
|
||||
|
||||
Assuming we already have a tool like `difft`
|
||||
([difftastic](https://difftastic.wilfred.me.uk/introduction.html)) available on
|
||||
our machine, we can use it as a diff viewer for the various `git` commands that
|
||||
display a diff.
|
||||
|
||||
This requires a manual override which involve two pieces — an inline
|
||||
configuration of `diff.external` specifying the binary of the external differ
|
||||
and the `--ext-diff` flag which tells these commands to use the external diff
|
||||
binary.
|
||||
|
||||
Here is what `git show` looks like with `difft`:
|
||||
|
||||
```bash
|
||||
$ git -c diff.external=difft show --ext-diff
|
||||
```
|
||||
|
||||
Without the `--ext-diff` flag, it will fallback to the default differ despite
|
||||
`diff.external` being set.
|
||||
|
||||
See `man git-diff` and friends for the `--ext-diff` flag. See `man git-config`
|
||||
for `diff.external`.
|
||||
41
github-actions/use-labels-to-block-pr-merge.md
Normal file
41
github-actions/use-labels-to-block-pr-merge.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Use Labels To Block PR Merge
|
||||
|
||||
Let's say our GitHub project has custom tags for both `no merge` and `wip`
|
||||
(_work in progress_). Whenever either of those labels has been applied to a PR,
|
||||
we want there to be a failed check so as to block the merge. This is useful to
|
||||
ensure automated tools (as well as someone not looking closely enough) don't
|
||||
merge a PR that isn't _ready to go_.
|
||||
|
||||
This can be achieved with a basic GitHub Actions workflow that requires no
|
||||
3rd-party actions. We can add the following as
|
||||
`.github/workflows/block-labeled-prs.yml` in our project.
|
||||
|
||||
```yaml
|
||||
name: Block Labeled PR Merges
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled, opened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
prevent-merge:
|
||||
if: ${{ contains(github.event.*.labels.*.name, 'no merge') || contains(github.event.*.labels.*.name, 'wip') }}
|
||||
name: Prevent Merging
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for label
|
||||
run: |
|
||||
echo "Pull request label prevents merging."
|
||||
echo "Labels: ${{ join(github.event.*.labels.*.name, ', ') }}"
|
||||
echo "Remove the blocking label(s) to skip this check."
|
||||
exit 1
|
||||
```
|
||||
|
||||
This workflow is run when a pull request is opened, when it is edited or
|
||||
synchronized, and when a label change is made. The job `prevent-merge` sees if
|
||||
any of the label names match `no merge` or `wip`. If so, we echo out some
|
||||
details in the ubuntu container and then `exit 1` to fail the check.
|
||||
|
||||
Shoutout to [Jesse Squire's
|
||||
implementation](https://www.jessesquires.com/blog/2021/08/24/useful-label-based-github-actions-workflows/#updated-21-march-2022)
|
||||
which I've heavily borrowed from here.
|
||||
63
go/basic-delve-debugging-session.md
Normal file
63
go/basic-delve-debugging-session.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Basic Delve Debugging Session
|
||||
|
||||
When using [delve](https://github.com/go-delve/delve) to debug a Go program,
|
||||
these are the series of things I usually find myself doing.
|
||||
|
||||
First, I start running the program with `dlv` including any arguments after a `--` (in my case, the `solve` subcommand and a filename).
|
||||
|
||||
```bash
|
||||
$ dlv debug . -- solve samples/001.txt
|
||||
```
|
||||
|
||||
`dlv` starts up and is ready to run my program from the beginning. I'll need to
|
||||
set a couple breakpoints before continuing. I do this with the `break` command,
|
||||
specifying the filename and line number.
|
||||
|
||||
```
|
||||
(dlv) break main.go:528
|
||||
Breakpoint 1 set at 0x10c1a5bea for main.traversePuzzleIterative() ./main.go:528
|
||||
(dlv) break main.go:599
|
||||
Breakpoint 2 set at 0x10c1a6dcc for main.traversePuzzleIterative() ./main.go:599
|
||||
```
|
||||
|
||||
Now I can continue which will run the program until hitting a breakpoint.
|
||||
|
||||
```
|
||||
(dlv) continue
|
||||
> [Breakpoint 2] main.traversePuzzleIterative() ./main.go:599 (hits goroutine(1):1 total:1) (PC: 0x10c1a6dcc)
|
||||
594: }
|
||||
595: }
|
||||
596:
|
||||
597: topStackFrame := stack[len(stack)-1]
|
||||
598: // if the current stack frame has more values, try the next
|
||||
=> 599: if len(topStackFrame.PossibleValues) > 0 {
|
||||
600: nextValue := topStackFrame.PossibleValues[0]
|
||||
601: topStackFrame.PossibleValues = topStackFrame.PossibleValues[1:]
|
||||
602: topStackFrame.CurrValue = nextValue
|
||||
603:
|
||||
604: // Undo the last placement and make a new one
|
||||
```
|
||||
|
||||
I can see the context around the line we've stopped on. From here I can dig
|
||||
into the current state of the program by looking at local variables (`locals`)
|
||||
or printing out a specific value (`print someVar`). I can continue to step
|
||||
through the program line by line with `next` or eventually run `continue` to
|
||||
proceed to the next breakpoint.
|
||||
|
||||
```
|
||||
(dlv) locals
|
||||
diagnostics = main.Diagnostics {BacktrackCount: 0, NodeVisitCount: 1, ValidityCheckCount: 2,...+2 more}
|
||||
stack = []main.StackData len: 1, cap: 1, [...]
|
||||
emptyCellPositions = [][]int len: 3, cap: 4, [...]
|
||||
emptyCellIndex = 1
|
||||
status = "Invalid"
|
||||
topStackFrame = main.StackData {RowIndex: 1, ColumnIndex: 7, PossibleValues: []int len: 8, cap: 8, [...],...+1 more}
|
||||
(dlv) print topStackFrame
|
||||
main.StackData {
|
||||
RowIndex: 1,
|
||||
ColumnIndex: 7,
|
||||
PossibleValues: []int len: 8, cap: 8, [2,3,4,5,6,7,8,9],
|
||||
CurrValue: 1,}
|
||||
(dlv) next
|
||||
> main.traversePuzzleIterative() ./main.go:600 (PC: 0x10c1a6dea)
|
||||
```
|
||||
41
go/check-if-cobra-flag-was-set.md
Normal file
41
go/check-if-cobra-flag-was-set.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Check If Cobra Flag Was Set
|
||||
|
||||
When using [Cobra](https://github.com/spf13/cobra) to define a CLI, we can
|
||||
specify a flag for a command like so:
|
||||
|
||||
```go
|
||||
var Seed int64
|
||||
myCmd.PersistentFlags().Int64VarP(&Seed, "seed", "", -1, "set a seed")
|
||||
```
|
||||
|
||||
This `--seed` flag has a _default_ of `-1`. If the flag isn't specified, then
|
||||
when we access that flag's value, we'll get `-1`.
|
||||
|
||||
But how do we differentiate between the _default_ `-1` and someone passing `-1`
|
||||
to the `--seed` flag when running the program?
|
||||
|
||||
In the command definition, we can look at the flags and see, by name, if
|
||||
specific ones were changed by user input rather than being the defaults.
|
||||
|
||||
```go
|
||||
myCommand := &cobra.Command{
|
||||
// coommand setup ...
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if cmd.Flags().Changed("seed") {
|
||||
seed, err := cmd.Flags().GetInt64("seed")
|
||||
if err != nil {
|
||||
fmt.Println("Seed flag is missing from `cmdFlags()`")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("Seed was set to %d\n", seed)
|
||||
} else {
|
||||
fmt.Println("Seed was not set")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If we don't want to rely on the default and instead want to specify some other
|
||||
behavior when the flag is not manually set by the user, we can detect that
|
||||
scenario like this.
|
||||
29
go/configure-max-string-print-length-for-delve.md
Normal file
29
go/configure-max-string-print-length-for-delve.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Configure Max String Print Length For Delve
|
||||
|
||||
During a [Delve](https://github.com/go-delve/delve) debugging session, we can
|
||||
print out the value of a given variable with the `print` command. Similarly, we
|
||||
can see the values of all local variables with the `locals` command.
|
||||
|
||||
Whenever Delve is printing out strings and slices, it will truncate what it
|
||||
displays to 64 characters (or items) by default.
|
||||
|
||||
```go
|
||||
(dlv) print diagnostics.Solutions[0]
|
||||
"295743861\n431865972\n876192543\n387459216\n612387495\n549216738\n7635...+25 more"
|
||||
```
|
||||
|
||||
This can be overridden by [changing the `config` of
|
||||
`max-string-len`](https://github.com/derekparker/delve/blob/237c5026f40e38d2dd6f62a7362de7b25b00c1c7/Documentation/cli/expr.md?plain=1#L59)
|
||||
to something longer. In my case here, all I need are about 90 characters to
|
||||
display my full string, so run `config max-string-len 90` from the `dlv`
|
||||
session.
|
||||
|
||||
```go
|
||||
(dlv) config max-string-len 90
|
||||
(dlv) print diagnostics.Solutions[0]
|
||||
"295743861\n431865972\n876192543\n387459216\n612387495\n549216738\n763524189\n928671354\n154938627"
|
||||
```
|
||||
|
||||
Now I can see the entire string instead of the truncated version.
|
||||
|
||||
[source](https://stackoverflow.com/a/52416264/535590)
|
||||
50
go/connect-to-a-sqlite-database.md
Normal file
50
go/connect-to-a-sqlite-database.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Connect To A SQLite Database
|
||||
|
||||
Using the `database/sql` module and the `github.com/mattn/go-sqlite3` package,
|
||||
we can connect to a SQLite database and run some queries. In my case, I have a
|
||||
SQLite connection string exported to my environment, so I can access that with
|
||||
`os.Getenv`. It's a local SQLite file, `./test.db`.
|
||||
|
||||
Calling `sql.Open`, I'm able to connect with a SQLite3 driver to the database
|
||||
at that connection string. The `setupDatabase` function returns that database
|
||||
connection pointer. Things like `Exec` and `QueryRow` can be called on `db`. I
|
||||
also need to make sure I close the connection to the database with a `defer`.
|
||||
|
||||
Here is a full example of connecting to a local SQLite database and inserting a
|
||||
record:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func setupDatabase() *sql.DB {
|
||||
databaseString := os.Getenv("GOOSE_DBSTRING")
|
||||
if len(databaseString) == 0 {
|
||||
fmt.Println("Error retrieving `GOOSE_DBSTRING` from env")
|
||||
os.Exit(1)
|
||||
}
|
||||
db, err := sql.Open("sqlite3", databaseString)
|
||||
if err != nil {
|
||||
fmt.Printf("Error opening database: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func main() {
|
||||
db := setupDatabase()
|
||||
defer db.Close()
|
||||
|
||||
sql := `insert into users (name) values (?);`
|
||||
|
||||
db.Exec(sql, "Josh")
|
||||
}
|
||||
```
|
||||
44
go/create-a-slice-from-an-array.md
Normal file
44
go/create-a-slice-from-an-array.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Create A Slice From An Array
|
||||
|
||||
Slices in Go are a flexible abstraction over arrays. We can create a slice from
|
||||
an array with the `[n:m]` _slicing_ syntax. We specify the left and right
|
||||
(exclusive) bounds of the array that we want to create the slice relative to.
|
||||
|
||||
We can exclude the lower bound which translates to the `0` index of the array.
|
||||
We can exclude the left bound which translates to the end of the array. We can
|
||||
even exclude both ends of the _slicing_ syntax which means creating a slice of
|
||||
the entire array.
|
||||
|
||||
Here is an example of each of those:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
arr := [...]string{
|
||||
"taco",
|
||||
"burrito",
|
||||
"torta",
|
||||
"enchilada",
|
||||
"quesadilla",
|
||||
"pozole",
|
||||
}
|
||||
|
||||
firstTwo := arr[:2]
|
||||
lastTwo := arr[len(arr)-2:]
|
||||
all := arr[:]
|
||||
|
||||
fmt.Println("First two:", firstTwo)
|
||||
// First two: [taco burrito]
|
||||
|
||||
fmt.Println("Last two:", lastTwo)
|
||||
// Last two: [quesadilla pozole]
|
||||
|
||||
fmt.Println("All:", all)
|
||||
// All: [taco burrito torta enchilada quesadilla pozole
|
||||
}
|
||||
```
|
||||
|
||||
[source](https://go.dev/blog/slices-intro#slices)
|
||||
59
go/detect-if-stdin-comes-from-a-redirect.md
Normal file
59
go/detect-if-stdin-comes-from-a-redirect.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Detect If Stdin Comes From A Redirect
|
||||
|
||||
Reading lines of input from `stdin` is flexible. And we may need our program to
|
||||
behave differently depending on where that input is coming from. For instance,
|
||||
if data is redirected or piped to our program, we scan and process it directly.
|
||||
Otherwise, we need to prompt the user to enter in specific info and go from
|
||||
there.
|
||||
|
||||
We can detect whether [`os.Stdin`](https://pkg.go.dev/os#pkg-variables) is
|
||||
being piped to, redirected to, or whether we should prompt the user by looking
|
||||
at the file mode descriptor of
|
||||
[`os.Stdin.Stat()`](https://pkg.go.dev/os#File.Stat).
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
file, err := os.Stdin.Stat()
|
||||
if err != nil {
|
||||
fmt.Printf("Error checking stdin: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fromTerminal := (file.Mode() & os.ModeCharDevice) != 0
|
||||
fromAPipe := (file.Mode() & os.ModeNamedPipe) != 0
|
||||
|
||||
if fromTerminal {
|
||||
fmt.Println("This is Char Device mode, let's prompt user for input")
|
||||
termScanner := bufio.NewScanner(os.Stdin)
|
||||
for termScanner.Scan() {
|
||||
fmt.Printf("- %s\n", termScanner.Text())
|
||||
break;
|
||||
}
|
||||
} else if fromAPipe {
|
||||
fmt.Println("This is Named Pipe mode, contents piped in")
|
||||
pipeScanner := bufio.NewScanner(os.Stdin)
|
||||
for pipeScanner.Scan() {
|
||||
fmt.Printf("- %s\n", pipeScanner.Text())
|
||||
}
|
||||
} else {
|
||||
fmt.Println("This means the input was redirected")
|
||||
redirectScanner := bufio.NewScanner(os.Stdin)
|
||||
for redirectScanner.Scan() {
|
||||
fmt.Printf("- %s\n", redirectScanner.Text())
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If `os.ModeCharDevice` then we are connected to a character device, like the
|
||||
terminal. We can see if input is being piped in by checking against
|
||||
`os.ModeNamedPipe`. Otherwise, there are a variety of file modes and I'm
|
||||
willing to assume we're dealing with a regular file redirect at that point.
|
||||
49
go/deterministically-seed-a-random-number-generator.md
Normal file
49
go/deterministically-seed-a-random-number-generator.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Deterministically Seed A Random Number Generator
|
||||
|
||||
If you need a random number in Go, you can always reach for the various
|
||||
functions in the `rand` package.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
for range 5 {
|
||||
roll := rand.Intn(6) + 1
|
||||
fmt.Printf("- %d\n", roll)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Each time I run that, I get a random set of values. Often in programming, we
|
||||
want some control over the randomness. We want to _seed_ the randomness so that
|
||||
it is deterministic. We want random, but the kind of random where we know how
|
||||
we got there.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
func main() {
|
||||
seed := int64(123)
|
||||
src := rand.NewSource(seed)
|
||||
rng := rand.New(src)
|
||||
|
||||
for range 5 {
|
||||
roll := rng.Intn(6) + 1
|
||||
fmt.Printf("- %d\n", roll)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In this second snippet, we create a `Source` with a specific seed value that we
|
||||
can use with a custom `Rand` struct. We can then deterministically get random
|
||||
numbers from it.
|
||||
55
go/difference-between-slice-and-pointer-to-slice.md
Normal file
55
go/difference-between-slice-and-pointer-to-slice.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Difference Between Slice And Pointer To Slice
|
||||
|
||||
Though a slice can be thought of and used as a flexible, variable-length
|
||||
array-like data structure, it is important to understand that it is also a
|
||||
special kind of pointer to an underlying array.
|
||||
|
||||
This matters when we a function receives a slice versus a pointer to a slice as
|
||||
an argument, depending on what it is doing with that slice.
|
||||
|
||||
If the function is access or updating elements in the slice, there is no
|
||||
difference. There is no meaningful difference between these two functions and
|
||||
we might as well use the former.
|
||||
|
||||
```go
|
||||
func replaceAtIndex(slice []string, index int, value string) {
|
||||
slice[index] = value
|
||||
}
|
||||
|
||||
func replaceAtIndexPtr(slice *[]string, index int, value string) {
|
||||
(*slice)[index] = value
|
||||
}
|
||||
```
|
||||
|
||||
On the other hand, if the receiving function needs to append to or replace the
|
||||
slice, then we need to pass a pointer to the slice. A direct slice argument
|
||||
will result in only the function-local copy getting replaced.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s1 := []int{8, 6, 7, 9}
|
||||
s2 := []int{8, 6, 7, 9}
|
||||
|
||||
addItem(s1, 11)
|
||||
fmt.Printf("s1: %v\n", s1) //=> s1: [8 6 7 9]
|
||||
|
||||
addItemPtr(&s2, 11)
|
||||
fmt.Printf("s2: %v\n", s2) //=> s2: [8 6 7 9 11]
|
||||
}
|
||||
|
||||
func addItem(slice []int, value int) {
|
||||
slice = append(slice, value)
|
||||
}
|
||||
|
||||
func addItemPtr(slice *[]int, value int) {
|
||||
(*slice) = append(*slice, value)
|
||||
}
|
||||
```
|
||||
|
||||
[source](https://go.dev/tour/moretypes/8)
|
||||
51
go/format-date-and-time-with-time-constants.md
Normal file
51
go/format-date-and-time-with-time-constants.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Format Date And Time With Time Constants
|
||||
|
||||
The Go [`time` package](https://pkg.go.dev/time) has a [`Format`
|
||||
function](https://pkg.go.dev/time#Time.Format) for displaying the parts of a
|
||||
date and time in standard and custom ways. It works a bit different than you
|
||||
might be used to from other languages. Rather than using `strftime` identifiers
|
||||
like in this string `"%B %d, %Y"`, there is a canonical date that is used as a
|
||||
reference point.
|
||||
|
||||
That canonical date is from Janary 2nd, 2006. That was a Monday. It was at 5
|
||||
seconds after 3:04PM. The Unix format of it looks like `"Mon Jan _2 15:04:05
|
||||
MST 2006"`.
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// This specific time pulled from `time.Format` docs
|
||||
t, _ := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 2015")
|
||||
|
||||
// Reference date and time:
|
||||
// "Mon Jan _2 15:04:05 MST 2006"
|
||||
|
||||
strf1 := t.Format("|2006|02|01|03:04:05|Day: Mon|")
|
||||
fmt.Println("strf1:", strf1)
|
||||
// strf1: |2015|25|02|11:06:39|Day: Wed|
|
||||
|
||||
strf2 := t.Format(time.DateTime)
|
||||
strf3 := t.Format(time.RubyDate)
|
||||
strf4 := t.Format(time.Kitchen)
|
||||
|
||||
fmt.Println("DateTime:", strf2) // DateTime: 2015-02-25 11:06:39
|
||||
fmt.Println("RubyDate:", strf3) // RubyDate: Wed Feb 25 11:06:39 +0000 2015
|
||||
fmt.Println("Kitchen:", strf4) // Kitchen: 11:06AM
|
||||
}
|
||||
```
|
||||
|
||||
Though there are a [variety of useful formatting
|
||||
constants](https://pkg.go.dev/time#pkg-constants) already available like
|
||||
`DateTime`, `RubyDate`, `Kitchen`, etc., we can also define our own formatting
|
||||
string by using the reference values for each part of a date and time.
|
||||
|
||||
If you want to reference the year, whether as `YYYY` or `YY`, it is always
|
||||
going to be a form of `2006`, so `2006` or `06` respectively. Even though the
|
||||
above time variable is in February, our format strings will always need to use
|
||||
one of `Jan`, `January`, `01` or `1`.
|
||||
65
go/pass-a-struct-to-a-function.md
Normal file
65
go/pass-a-struct-to-a-function.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Pass A Struct To A Function
|
||||
|
||||
Go operates as _pass-by-value_ which means that when we pass a struct to a
|
||||
function, the receiving function gets a copy of the struct. Two things worth
|
||||
noticing about that are 1) an extra memory allocation happens when calling the
|
||||
function and 2) altering the struct does not affect the original in the calling
|
||||
context.
|
||||
|
||||
On the other hand, we can have a function that takes a pointer to a struct.
|
||||
When we call that function, we have a reference to the memory location of the
|
||||
struct instead of a copy of the struct. That means no additional allocation and
|
||||
modifications to the dereferenced struct are modifications to the original in
|
||||
the calling context.
|
||||
|
||||
Here is an example that demonstrates both of these. Notice the printed output
|
||||
that is included in comments at the end which shows memory locations and
|
||||
contents of the struct at various points.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Order struct {
|
||||
Item string
|
||||
Quantity int
|
||||
DineIn bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
order := Order{Item: "taco", Quantity: 3, DineIn: true}
|
||||
|
||||
fmt.Println("Order:", order)
|
||||
fmt.Printf("main - Loc: %p\n", &order)
|
||||
|
||||
doubledOrder := doubleOrder(order)
|
||||
|
||||
fmt.Println("Double Order:", doubledOrder)
|
||||
fmt.Println("Original Order:", order)
|
||||
|
||||
doubleOrderPtr(&order)
|
||||
|
||||
fmt.Println("Double Order Ptr:", order)
|
||||
}
|
||||
|
||||
func doubleOrder(order Order) Order {
|
||||
fmt.Printf("doubleOrder - Loc: %p\n", &order)
|
||||
order.Quantity *= 2
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
func doubleOrderPtr(order *Order) {
|
||||
fmt.Printf("doubleOrderPtr - Loc: %p\n", order)
|
||||
(*order).Quantity *= 2
|
||||
}
|
||||
|
||||
// Order: {taco 3 true}
|
||||
// main - Loc: 0xc0000b4000
|
||||
// doubleOrder - Loc: 0xc0000b4040
|
||||
// Double Order: {taco 6 true}
|
||||
// Original Order: {taco 3 true}
|
||||
// doubleOrderPtr - Loc: 0xc0000b4000
|
||||
// Double Order Ptr: {taco 6 true}
|
||||
```
|
||||
32
go/produce-the-zero-value-of-a-generic-type.md
Normal file
32
go/produce-the-zero-value-of-a-generic-type.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Produce The Zero Value For A Generic Type
|
||||
|
||||
While writing a _pop_ function that would work with slices of a generic type, I
|
||||
ran into the issue of needing to produce a zero value of type `T` when
|
||||
returning early for an empty slice.
|
||||
|
||||
The way to arbitrarily get the zero value of a generic in Go is with `*new(T)`.
|
||||
|
||||
I was able to use this in my `Pop` function like so:
|
||||
|
||||
```go
|
||||
func Pop[T any](slice []T) (T, error) {
|
||||
if len(slice) == 0 {
|
||||
return *new(T), fmt.Errorf("cannot pop an empty slice")
|
||||
}
|
||||
|
||||
lastItem := slice[len(slice)-1]
|
||||
|
||||
slice = slice[:len(slice)-1]
|
||||
|
||||
return lastItem, nil
|
||||
}
|
||||
```
|
||||
|
||||
If this is happening in multiple functions and we want a more self-documenting
|
||||
approach, we can pull it out into a function `zero`:
|
||||
|
||||
```go
|
||||
func zero[T any]() T {
|
||||
return *new(T)
|
||||
}
|
||||
```
|
||||
39
go/redirect-file-to-stdin-during-delve-debug.md
Normal file
39
go/redirect-file-to-stdin-during-delve-debug.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Redirect File To Stdin During Delve Debug
|
||||
|
||||
I have a go program that accepts input from stdin. The way I've been running
|
||||
the program as I develop it is to redirect the output of some sample files to
|
||||
the program.
|
||||
|
||||
```bash
|
||||
$ go run . < sample/001.txt
|
||||
```
|
||||
|
||||
When I then go to debug this program with
|
||||
[Delve](https://github.com/go-delve/delve), I'd still like to be able to
|
||||
redirect a file into the program to reproduce the exact behavior I'm seeing.
|
||||
|
||||
The following won't work:
|
||||
|
||||
```bash
|
||||
$ dlv debug . < samples/001.txt
|
||||
Stdin is not a terminal, use '-r' to specify redirects for the target process or --allow-non-terminal-interactive=true if you really want to specify a redirect for Delve
|
||||
```
|
||||
|
||||
Fortunately, `dlv` sees what I'm trying to do and makes a recommendation. The
|
||||
`-r` flag can be used to specify redirects for the target process. The [`dlv`
|
||||
redirect
|
||||
docs](https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_redirect.md)
|
||||
explain that `-r` can be passed a `source:destination`. The `source` is `stdin`
|
||||
by default, but can also be `stdout` and `stderr`.
|
||||
|
||||
I can redirect my file into the debugging session of my program like so:
|
||||
|
||||
```bash
|
||||
$ dlv debug . -r stdin:samples/001.txt
|
||||
```
|
||||
|
||||
Or even more succinctly:
|
||||
|
||||
```bash
|
||||
$ dlv debug . -r samples/001.txt
|
||||
```
|
||||
58
go/write-a-custom-scan-function-for-file-io.md
Normal file
58
go/write-a-custom-scan-function-for-file-io.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Write A Custom Scan Function For File IO
|
||||
|
||||
By default a [`bufio.Scanner`](https://pkg.go.dev/bufio#Scanner) will scan
|
||||
input line-by-line. In other words, splitting on newlines such that each
|
||||
iteration will emit everything up to the next newline character.
|
||||
|
||||
We can write our own `SplitFunc` and override the default one by calling
|
||||
`scanner.Split` with it. Our custom scan function needs to match the type
|
||||
signature of [`SplitFunc`](https://pkg.go.dev/bufio#SplitFunc).
|
||||
|
||||
Here is a custom one that emits each individual character but omits the
|
||||
newlines.
|
||||
|
||||
```go
|
||||
func ScanChar(data []byte, atEOF bool) (int, []byte, error) {
|
||||
if atEOF || len(data) == 0 {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
start := 0
|
||||
for start < len(data) {
|
||||
if !utf8.FullRune(data[start:]) {
|
||||
return 0, nil, nil
|
||||
}
|
||||
|
||||
r, size := utf8.DecodeRune(data[start:])
|
||||
if r == utf8.RuneError {
|
||||
return 0, nil, fmt.Errorf("invalid UTF-8 encoding")
|
||||
}
|
||||
|
||||
if r != '\n' {
|
||||
return start + size, data[start:start+size], nil
|
||||
}
|
||||
|
||||
// found a \n, advance the start position
|
||||
start += size
|
||||
}
|
||||
|
||||
return start, nil, nil
|
||||
}
|
||||
```
|
||||
|
||||
We can then use thi `ScanChar` function with a `bufio.Scanner` like so:
|
||||
|
||||
```go
|
||||
func ReadFileByCharacter(file io.Reader) {
|
||||
scanner := bufio.NewScanner(file)
|
||||
|
||||
// override default SplitFunc
|
||||
scanner.Split(scanChar)
|
||||
|
||||
for scanner.Scan() {
|
||||
char := scanner.Text()
|
||||
|
||||
fmt.Printf("- %s\n", char)
|
||||
}
|
||||
}
|
||||
```
|
||||
28
html/disclose-additional-details.md
Normal file
28
html/disclose-additional-details.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Disclose Additional Details
|
||||
|
||||
You can add extra details to an HTML page that are only disclosed if the user
|
||||
chooses to disclose them. To do that, we use the `<details>` tag. This tag
|
||||
needs to have a `<summary>` tag nested within it. Anything else nested within
|
||||
`<details>` will be what is disclosed when it is toggled open. The `<summary>`
|
||||
is what is displayed when it is not open.
|
||||
|
||||
Here is a `<detail>` block I recently added to [Ruby Operator
|
||||
Lookup](https://www.visualmode.dev/ruby-operators).
|
||||
|
||||
```html
|
||||
<details className="pt-2 pb-6">
|
||||
<summary>What is this thing?</summary>
|
||||
<p className="pl-3 pt-2 text-gray-700 text-sm">
|
||||
Ruby is an expressive, versatile, and flexible dynamic programming language. That means there are all kinds of syntax features, operators, and symbols we can encounter that might look unfamiliar and are hard to look up. Ruby Operator Lookup is a directory of all these language features.
|
||||
</p>
|
||||
<p className="pl-3 pt-2 text-gray-700 text-sm">
|
||||
Use the search bar to narrow down the results. Then click on a button for the operator or symbol you want to explore further.
|
||||
</p>
|
||||
</details>
|
||||
```
|
||||
|
||||
On page load, the only thing we see is "What is this thing?" with a triangle
|
||||
symbol next to it. If we click the summary, then the entire details block
|
||||
(those two `<p>` tags) are disclosed.
|
||||
|
||||
[source](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/details)
|
||||
37
mac/start-amphetamine-session-with-applescript.md
Normal file
37
mac/start-amphetamine-session-with-applescript.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Start Amphetamine Session With AppleScript
|
||||
|
||||
I use the _Amphetamine_ app on Mac to keep my computer from going to sleep
|
||||
during the day. It is a menu bar app that can be used to start a _Session_ of
|
||||
time where it will keep your computer from going to sleep. At the start of my
|
||||
day, I'll typically start an 8 hour _Session_. This is useful if I have to step
|
||||
away fo 10 minutes or if I'm doing some writing in my notebook, my computer
|
||||
won't go to sleep on me.
|
||||
|
||||
Though these sessions can be controlled from the menu bar app, I was excited to
|
||||
learn that I can also programatically start a session with AppleScript.
|
||||
|
||||
Here is how to start a _Session_ (overriding an existing session) with options
|
||||
that specify it is 8 hours long and the display should not be allowed to sleep.
|
||||
|
||||
```bash
|
||||
$ osascript -e 'tell application "Amphetamine" to start new session with options {duration:8, interval:hours, displaySleepAllowed:false}'
|
||||
```
|
||||
|
||||
The `interval` could also be `minutes` and then I could change the duration to
|
||||
an amount of time that makes sense in minutes, e.g. `90` for 1.5 hours.
|
||||
|
||||
Note: the `with options {...}` segement is all or nothing. All three need to be included or don't include the clause at all.
|
||||
|
||||
Additionally, a session of indefinite duration can be started by including no options:
|
||||
|
||||
```bash
|
||||
$ osascript -e 'tell application "Amphetamine" to start new session'
|
||||
```
|
||||
|
||||
And any existing session can be ended with:
|
||||
|
||||
```bash
|
||||
$ osascript -e 'tell application "Amphetamine" to end session'
|
||||
```
|
||||
|
||||
[source](https://iffy.freshdesk.com/support/solutions/articles/48000078223-applescript-documentation)
|
||||
17
mac/uninstall-logitech-g-hub-from-mac.md
Normal file
17
mac/uninstall-logitech-g-hub-from-mac.md
Normal file
@@ -0,0 +1,17 @@
|
||||
# Uninstall LogiTech G Hub From Mac
|
||||
|
||||
I rarely uninstall software from my Mac. And unless the software is nice enough
|
||||
to provide a clear 'Uninstall' flow, it is not straightforward how to do it. In
|
||||
fact, it probably varies quite a bit from app to app.
|
||||
|
||||
In the case of LogiTech's G Hub, I was able to find the following instructions
|
||||
for uninstalling it. The thing of note is that the updater app can take an
|
||||
`--uninstall` flag.
|
||||
|
||||
```bash
|
||||
sudo /Applications/lghub.app/Contents/MacOS/lghub_updater.app/Contents/MacOS/lghub_updater --uninstall
|
||||
```
|
||||
|
||||
I still had to remove the app launcher from my `Applications` directory.
|
||||
|
||||
[source](https://www.reddit.com/r/LogitechG/comments/bluth5/comment/lbhctx1/)
|
||||
25
mac/use-a-different-font-with-iterm2.md
Normal file
25
mac/use-a-different-font-with-iterm2.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Use A Different Font With iTerm2
|
||||
|
||||
I wanted to give [`gh-dash`](https://github.com/dlvhdr/gh-dash) a try, but
|
||||
after installing and opening it up, I was seeing a bunch of `?` characters
|
||||
where specialized font icons were missing. Their README recommended installing
|
||||
a [`Nerd Font`](https://github.com/ryanoasis/nerd-fonts) that includes those
|
||||
icons, such as [`Fira Code`](https://github.com/tonsky/FiraCode).
|
||||
|
||||
I was able to install `font-fira-code-nerd-font` with homebrew:
|
||||
|
||||
```bash
|
||||
$ brew install font-fira-code-nerd-font
|
||||
```
|
||||
|
||||
Then to get iTerm2 to start using that font, I had to change the font setting
|
||||
for my current profile.
|
||||
|
||||
Under the _iTerm2_ menu is _Settings..._. From there, I clicked the _Profiles_
|
||||
section. For the _Default_ profile, I went to the _Text_ tab and under _Font_ I
|
||||
selected _FireCode Nerd Font Mono_ from the dropdown.
|
||||
|
||||
That won't take effect on any current iTerm2 windows. Since I have everything
|
||||
running through `tmux`, I could close my current window, open a new one
|
||||
(`Cmd+N`), and reconnect to my existing `tmux` session. Now when I run `gh
|
||||
dash`, I see all the font icons that were missing before.
|
||||
56
postgres/count-the-number-of-items-in-an-array.md
Normal file
56
postgres/count-the-number-of-items-in-an-array.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Count The Number Of Items In An Array
|
||||
|
||||
There are two ways to count the number of items in an array with PostgreSQL.
|
||||
The one that might jump out at you or show up at the top of search results is
|
||||
[`array_length`](https://www.postgresql.org/docs/current/functions-array.html).
|
||||
|
||||
```sql
|
||||
> select array_length(array[1,2,3], 1);
|
||||
+--------------+
|
||||
| array_length |
|
||||
|--------------|
|
||||
| 3 |
|
||||
+--------------+
|
||||
|
||||
> select array_length(array[[1,2], [3,4]], 2);
|
||||
+--------------+
|
||||
| array_length |
|
||||
|--------------|
|
||||
| 2 |
|
||||
+--------------+
|
||||
```
|
||||
|
||||
This requires specifying the dimension at which you want to check the length.
|
||||
The first example, checking the 1st dimension of a one-dimensional array, seems
|
||||
like the more common and useful scenario. In the second example, we are
|
||||
checking the 2nd dimension.
|
||||
|
||||
The other way we can determine the number of items in an array is with the
|
||||
[`cardinality`](https://www.postgresql.org/docs/current/functions-array.html)
|
||||
function.
|
||||
|
||||
> Returns the total number of elements in the array, or 0 if the array is
|
||||
> empty.
|
||||
|
||||
```sql
|
||||
> select cardinality(array[1,2,3]);
|
||||
+-------------+
|
||||
| cardinality |
|
||||
|-------------|
|
||||
| 3 |
|
||||
+-------------+
|
||||
|
||||
> select cardinality(array[[1,2], [3,4]]);
|
||||
+-------------+
|
||||
| cardinality |
|
||||
|-------------|
|
||||
| 4 |
|
||||
+-------------+
|
||||
```
|
||||
|
||||
This behaves the same as `array_length` for a one-dimensional array and doesn't
|
||||
require a second argument. Where it gets more interesting is with
|
||||
multi-dimensional arrays. It returns the total number of elements in the
|
||||
arrayregardless of the nesting.
|
||||
|
||||
[source](https://mattrighetti.com/2025/01/20/you-dont-need-sql-builders)
|
||||
58
postgres/trim-leading-and-trailing-space-from-string.md
Normal file
58
postgres/trim-leading-and-trailing-space-from-string.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# Trim Leading And Trailing Space From String
|
||||
|
||||
PostgreSQL has a bunch of [string
|
||||
functions](https://www.postgresql.org/docs/current/functions-string.html),
|
||||
including several for doing various string trimming.
|
||||
|
||||
We can use the simplest form of `trim` to remove leading and trailing space
|
||||
characters from a string.
|
||||
|
||||
```sql
|
||||
> select trim(' Taco Cat ');
|
||||
+----------+
|
||||
| btrim |
|
||||
|----------|
|
||||
| Taco Cat |
|
||||
+----------+
|
||||
```
|
||||
|
||||
The syntax for calling `trim` is a bit odd relative to other PostgreSQL
|
||||
functions and functions in other languages. Here is the "grammar" as described
|
||||
in the docs:
|
||||
|
||||
```
|
||||
trim ( [ LEADING | TRAILING | BOTH ] [ characters text ] FROM string text ) → text
|
||||
```
|
||||
|
||||
We pick `leading`, `trailing`, or `both`, with `both` being the default. Then
|
||||
we specify the character(s) we want to remove. This is also optional, the
|
||||
default being the space character. Then we say `from` what string we want to
|
||||
trim those characters.
|
||||
|
||||
Here we remove all sequential spaces from `both` ends of the given string:
|
||||
|
||||
```sql
|
||||
> select trim(both from ' Taco Cat ');
|
||||
+----------+
|
||||
| btrim |
|
||||
|----------|
|
||||
| Taco Cat |
|
||||
+----------+
|
||||
```
|
||||
|
||||
To further demonstrate how `trim` works, here we remove all sequences made up
|
||||
of any of spaces, uppercase `T`, and lowercase `t` from `both` ends of the
|
||||
string:
|
||||
|
||||
```sql
|
||||
> select trim(both ' Tt' from ' Taco Cat ');
|
||||
+--------+
|
||||
| btrim |
|
||||
|--------|
|
||||
| aco Ca |
|
||||
+--------+
|
||||
```
|
||||
|
||||
Notice that in all the above examples the column name of the result is `btrim`.
|
||||
That's probably because `btrim` (_trim both ends_) is being called under the
|
||||
hood for the `both` option.
|
||||
45
postgres/unable-to-infer-data-type-in-production.md
Normal file
45
postgres/unable-to-infer-data-type-in-production.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Unable To Infer Data Type In Production
|
||||
|
||||
Inspired by [You Probably Don't Need Query
|
||||
Builders](https://mattrighetti.com/2025/01/20/you-dont-need-sql-builders), I
|
||||
wrote a query in one of my applications that has filter clauses that get
|
||||
short-circuited if the filter value hasn't been included.
|
||||
|
||||
That query looked something like this:
|
||||
|
||||
```ruby
|
||||
@tags =
|
||||
Tag.where("? is null or normalized_value ilike ?", normalized_query, "%#{normalized_query}%")
|
||||
.order(:normalized_value)
|
||||
.limit(10)
|
||||
```
|
||||
|
||||
The `normalized_value ilike ?` filtering won't be applied if the
|
||||
`normalized_query` value isn't present (`nil`). This helps me avoid writing
|
||||
messy ternaries or if-else conditional query building madness.
|
||||
|
||||
Unfortunately, when I shipped this query to production, the page started
|
||||
failing and Postgres was reporting this error in the logs.
|
||||
|
||||
```
|
||||
Caused by: PG::IndeterminateDatatype (ERROR: could not determine data type of parameter $1)
|
||||
```
|
||||
|
||||
The query is prepared as a parameterized statement and Postgres appears to be
|
||||
unable to determine the datatype of the first parameter (`$1`) —
|
||||
`normalized_query`.
|
||||
|
||||
I was unable to reproduce the issue in development. It was only occuring in
|
||||
production. Until I can come up with a root cause analysis, I have the
|
||||
following fix that does a casting to `text`. This helps out with the type
|
||||
inference and makes the issue go away.
|
||||
|
||||
```ruby
|
||||
@tags =
|
||||
Tag.where("cast(? as text) is null or normalized_value ilike ?", normalized_query, "%#{normalized_query}%")
|
||||
.order(:normalized_value)
|
||||
.limit(10)
|
||||
```
|
||||
|
||||
Interestingly, this person using `pgtyped` [ran into the exact same issue with
|
||||
the same type of query](https://github.com/adelsz/pgtyped/issues/354).
|
||||
22
rails/add-a-generated-column-to-a-postgresql-table.md
Normal file
22
rails/add-a-generated-column-to-a-postgresql-table.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Add A Generated Column To A PostgreSQL Table
|
||||
|
||||
As of Rails 7, ActiveRecord supports generated columns for app's backed by a
|
||||
PostgreSQL database. This is achieved with a `virtual` column.
|
||||
|
||||
```ruby
|
||||
class CreateTags < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
create_table :tags, id: :bigint do |t|
|
||||
t.string :value
|
||||
t.virtual :normalized_value, type: :text, as: "lower(value)", stored: true
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
With a table like this, any time we add a record with a `value`, PostgreSQL
|
||||
computes and stores the `normalized_value` column based on that.
|
||||
|
||||
[source](https://blog.saeloun.com/2022/01/25/rails-7-postgres-support-for-generated-columns/)
|
||||
40
rails/apply-basic-html-formatting-to-block-of-text.md
Normal file
40
rails/apply-basic-html-formatting-to-block-of-text.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Apply Basic HTML Formatting To Block Of Text
|
||||
|
||||
My Rails app has a form that allows a user to enter in free-form text. I enter
|
||||
in a couple paragraphs and save the record. It is rendered on a show page with
|
||||
a couple lines of ERB like so:
|
||||
|
||||
```ruby
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<div class="space-y-4">
|
||||
<div class="prose mt-8 text-gray-700">
|
||||
<%= @record.notes %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
When I view the erb-displayed version of that record's text, all those
|
||||
carefully spaced paragraphs are clumped together. That is because those newline
|
||||
(`\n` and `\n\n`) characters while understood to be whitespace do not have
|
||||
formatting implications in the browser like a combination of HTML tags and CSS
|
||||
do.
|
||||
|
||||
I can apply some basic formatting with [the aptly named `simple_format` method
|
||||
available as an `ActionView`
|
||||
helper](https://api.rubyonrails.org/classes/ActionView/Helpers/TextHelper.html#method-i-simple_format).
|
||||
|
||||
```ruby
|
||||
<div class="max-w-3xl mx-auto">
|
||||
<div class="space-y-4">
|
||||
<div class="prose mt-8 text-gray-700">
|
||||
<%= simple_format(@record.notes) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
This turns single `\n` characters into a `<br />` tag and double `\n\n` cause
|
||||
the surrounding paragraphs to be wrapped in `<p>` tags. That simple formatting
|
||||
combined with my existing TailwindCSS styles makes the formatting of my text
|
||||
immediately look much better.
|
||||
35
rails/determine-the-configured-primary-key-type.md
Normal file
35
rails/determine-the-configured-primary-key-type.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Determine The Configured Primary Key Type
|
||||
|
||||
I noticed an interesting helper function in the database migration generated by
|
||||
`bin/rails active_storage:install`.
|
||||
|
||||
```ruby
|
||||
class CreateActiveStorageTables < ActiveRecord::Migration[8.0]
|
||||
def change
|
||||
# Use Active Record's configured type for primary and foreign keys
|
||||
primary_key_type, foreign_key_type = primary_and_foreign_key_types
|
||||
|
||||
# ...
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def primary_and_foreign_key_types
|
||||
config = Rails.configuration.generators
|
||||
setting = config.options[config.orm][:primary_key_type]
|
||||
primary_key_type = setting || :primary_key
|
||||
foreign_key_type = setting || :bigint
|
||||
[ primary_key_type, foreign_key_type ]
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
The `primary_and_foreign_key_types` method looks in the generators config for
|
||||
the ORM (`:active_record`) to determine the configured `:primary_key_type`. By
|
||||
default this will return `nil`. This method then uses `:primary_key` as a
|
||||
fallback value which will be `bigint`. That's why the `foreign_key_type` falls
|
||||
back to `:bigint`.
|
||||
|
||||
If desired, this can be manually configured in `config/application.rb` like
|
||||
shown in the [ActiveRecord Migrations
|
||||
docs](https://guides.rubyonrails.org/active_record_migrations.html#enabling-uuids-in-rails).
|
||||
45
rails/inspect-configuration-of-database-connection.md
Normal file
45
rails/inspect-configuration-of-database-connection.md
Normal file
@@ -0,0 +1,45 @@
|
||||
# Inspect Configuration Of Database Connection
|
||||
|
||||
There are a lot of factors that can effect the database configuration values.
|
||||
|
||||
- What are the settings in each environment in `config/database.yml`?
|
||||
- Is there any dynamic ERB code in `config/database.yml`?
|
||||
- Is `DATABASE_URL` set in the current environment?
|
||||
- Is any other code overriding these settings?
|
||||
|
||||
To check the current _configuration hash_ for the database connection at
|
||||
runtime, we can run the following statement:
|
||||
|
||||
```ruby
|
||||
> ActiveRecord::Base.connection.pool.db_config.configuration_hash
|
||||
=>
|
||||
{:adapter=>"postgresql",
|
||||
:encoding=>"unicode",
|
||||
:host=>"::1",
|
||||
:user=>"postgres",
|
||||
:password=>"postgres",
|
||||
:pool=>5,
|
||||
:database=>"still_development",
|
||||
:port=>9875}
|
||||
```
|
||||
|
||||
In this case, I'm running the statement from the Rails console of my app's
|
||||
development environment.
|
||||
|
||||
I could even access and print these values as part of debugging in a production
|
||||
environment with a rake task:
|
||||
|
||||
```ruby
|
||||
# In lib/tasks/debug.rake
|
||||
namespace :debug do
|
||||
task :db_config => :environment do
|
||||
puts "==== Database Configuration Debug ===="
|
||||
puts "DATABASE_URL: #{ENV['DATABASE_URL']}"
|
||||
puts "Active Record Config: #{ActiveRecord::Base.connection.pool.db_config.configuration_hash}"
|
||||
puts "Raw ENV dump:"
|
||||
ENV.sort.each { |k,v| puts "#{k}: #{v}" if k.include?('DB') || k.include?('DATABASE') }
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
[source](https://api.rubyonrails.org/classes/ActiveRecord/DatabaseConfigurations/HashConfig.html)
|
||||
38
rails/override-text-displayed-by-form-label.md
Normal file
38
rails/override-text-displayed-by-form-label.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Override Text Displayed By Form Label
|
||||
|
||||
Rails does a good job with the default text displayed by a form label. It takes
|
||||
the primary symbol value you give it and capitalizes that. And that is often
|
||||
good enough.
|
||||
|
||||
```ruby
|
||||
<%= form_with(model: post) do |form| %>
|
||||
<%= form.label :title, class: "text-sm font-medium text-gray-700" %>
|
||||
<%= form.text_field :title, required: true, class: "..." %>
|
||||
<% end %>
|
||||
```
|
||||
|
||||
This will yield a label value of _Title_.
|
||||
|
||||
Sometimes, however, the casing needs to be different or you need entirely
|
||||
different text. Take this URL field for example. Rails will convert `:url` into
|
||||
_Url_ for the label text. Not ideal. I can override the default with a second
|
||||
positional argument, in this case, `"URL"`.
|
||||
|
||||
```ruby
|
||||
<%= form_with(model: post) do |form| %>
|
||||
<%= form.label :url, "URL", class: "text-sm font-medium text-gray-700" %>
|
||||
<%= form.url_field :url, required: true, class: "..." %>
|
||||
<% end %>
|
||||
```
|
||||
|
||||
The [Rails docs have another good
|
||||
example](https://guides.rubyonrails.org/form_helpers.html#a-generic-search-form).
|
||||
A label with a value of `query` that is overridden to display "Search for:".
|
||||
|
||||
```ruby
|
||||
<%= form_with url: "/search", method: :get do |form| %>
|
||||
<%= form.label :query, "Search for:" %>
|
||||
<%= form.search_field :query %>
|
||||
<%= form.submit "Search" %>
|
||||
<% end %>
|
||||
```
|
||||
29
rails/rebuild-tailwind-bundle-for-dev-server.md
Normal file
29
rails/rebuild-tailwind-bundle-for-dev-server.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Rebuild Tailwind Bundle For Dev Server
|
||||
|
||||
If you're using the TailwindCSS gem in your Rails app:
|
||||
|
||||
```ruby
|
||||
# Use Tailwind CSS [https://github.com/rails/tailwindcss-rails]
|
||||
gem "tailwindcss-rails"
|
||||
```
|
||||
|
||||
you may find that as you add and adjust styles in your views, refreshing the
|
||||
page doesn't take any styling effects. That is because the tailwind bundle gets
|
||||
built with just the style rules that were used at the time it was generated.
|
||||
|
||||
In development, as we're working, we expect the styles used by our app to
|
||||
actively changed. And we don't mind a little performance hit to have the bundle
|
||||
rebuilt. In that case, we can instruct `puma` to _Live Rebuild_ in
|
||||
`development` with the `tailwindcss` plugin.
|
||||
|
||||
```ruby
|
||||
# config/puma.rb
|
||||
|
||||
# Enable TailwindCSS rebuild in development
|
||||
plugin :tailwindcss if ENV.fetch("RAILS_ENV", "development") == "development"
|
||||
```
|
||||
|
||||
This has `rails server` run a watch process in the background that live
|
||||
rebuilds the bundle.
|
||||
|
||||
[source](https://github.com/rails/tailwindcss-rails?tab=readme-ov-file#puma-plugin)
|
||||
44
rails/scaffold-auth-functionality-with-rails-8-generator.md
Normal file
44
rails/scaffold-auth-functionality-with-rails-8-generator.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Scaffold Auth Functionality With Rails 8 Generator
|
||||
|
||||
Rails 8 added a built-in generator for authentication that scaffolds the core
|
||||
models, controllers, views, routes, etc. needed for a basic email/password
|
||||
authentication flow. It creates a `User` model, if one doesn't already exist,
|
||||
as the authenticated object. It uses the `bcrypt` gem for password hashing,
|
||||
etc.
|
||||
|
||||
Here is an example of what you get when running the generator on a relatively
|
||||
new Rails 8 project:
|
||||
|
||||
```bash
|
||||
$ bin/rails generate authentication
|
||||
invoke tailwindcss
|
||||
create app/views/passwords/new.html.erb
|
||||
create app/views/passwords/edit.html.erb
|
||||
create app/views/sessions/new.html.erb
|
||||
create app/models/session.rb
|
||||
create app/models/user.rb
|
||||
create app/models/current.rb
|
||||
create app/controllers/sessions_controller.rb
|
||||
create app/controllers/concerns/authentication.rb
|
||||
create app/controllers/passwords_controller.rb
|
||||
create app/channels/application_cable/connection.rb
|
||||
create app/mailers/passwords_mailer.rb
|
||||
create app/views/passwords_mailer/reset.html.erb
|
||||
create app/views/passwords_mailer/reset.text.erb
|
||||
create test/mailers/previews/passwords_mailer_preview.rb
|
||||
insert app/controllers/application_controller.rb
|
||||
route resources :passwords, param: :token
|
||||
route resource :session
|
||||
gsub Gemfile
|
||||
bundle install --quiet
|
||||
generate migration CreateUsers email_address:string!:uniq password_digest:string! --force
|
||||
rails generate migration CreateUsers email_address:string!:uniq password_digest:string! --force
|
||||
invoke active_record
|
||||
create db/migrate/20250115224625_create_users.rb
|
||||
generate migration CreateSessions user:references ip_address:string user_agent:string --force
|
||||
rails generate migration CreateSessions user:references ip_address:string user_agent:string --force
|
||||
invoke active_record
|
||||
create db/migrate/20250115224626_create_sessions.rb
|
||||
```
|
||||
|
||||
[source](https://www.bigbinary.com/blog/rails-8-introduces-a-basic-authentication-generator)
|
||||
52
rails/set-meta-tags-in-erb-views.md
Normal file
52
rails/set-meta-tags-in-erb-views.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Set Meta Tags In ERB Views
|
||||
|
||||
There are all kinds of meta tags that we may want to set for the pages that our
|
||||
Rails app serves. A lot of these are for SEO and social sharing. Let's look at
|
||||
how to add `og:description` meta tags to our views.
|
||||
|
||||
I'll start with a helper method in `app/helpers/application_helper.rb`:
|
||||
|
||||
```ruby
|
||||
module ApplicationHelper
|
||||
def meta_description(desc)
|
||||
content_for(:description) { desc }
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Then, I'll update my `app/views/layouts/application.html.erb` to consume the
|
||||
description when provided.
|
||||
|
||||
```ruby
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<!-- ... -->
|
||||
|
||||
<meta
|
||||
property="og:description"
|
||||
content="<%= content_for?(:description) ? yield(:description) : 'Default description' %>"
|
||||
>
|
||||
|
||||
<!-- ... -->
|
||||
</head>
|
||||
|
||||
<!-- ... -->
|
||||
</html>
|
||||
```
|
||||
|
||||
Now I have a default description for all my views that I can override as needed
|
||||
with the `meta_description` helper.
|
||||
|
||||
```ruby
|
||||
# app/views/posts/show.html.erb
|
||||
<%= meta_description @post.body.split("\n").first %>
|
||||
|
||||
<!-- ... -->
|
||||
```
|
||||
|
||||
If I reload the page and inspect the meta tags in `<head>`, I should find the
|
||||
`og:description` tag with the corresponding value.
|
||||
|
||||
This can be extended to apply all the different meta tags (e.g. Open Graph and
|
||||
Twitter) to make links to these pages render well across the internet.
|
||||
22
ruby/clamp-to-an-endless-range.md
Normal file
22
ruby/clamp-to-an-endless-range.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Clamp To An Endless Range
|
||||
|
||||
The
|
||||
[`Comparable#clamp`](https://ruby-doc.org/3.3.6/Comparable.html#method-i-clamp)
|
||||
method allows us to specify the bounds of a value we want. If the target value
|
||||
is between the bounds, then we get that value. Otherwise, we gets the nearest
|
||||
end of the bounds.
|
||||
|
||||
We can even pass a range to `#clamp` instead of separate lower and upper bound
|
||||
values. Because Ruby has beginless and endless ranges, this gives us the
|
||||
ergonomics to, say, clamp to any non-negative value with a `0..` endless range.
|
||||
|
||||
Here is what that looks like:
|
||||
|
||||
```ruby
|
||||
> 22.clamp(0..)
|
||||
=> 22
|
||||
> (-33).clamp(0..)
|
||||
=> 0
|
||||
> 0.clamp(0..)
|
||||
=> 0
|
||||
```
|
||||
37
ruby/extract-capture-group-matches-with-string-slices.md
Normal file
37
ruby/extract-capture-group-matches-with-string-slices.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Extract Capture Group Matches With String Slices
|
||||
|
||||
Ruby's _string slice_ syntax allows us to use the square brackets to access
|
||||
portions of a string. It's most common to pass positional integer index
|
||||
arguments or a range. However, in true Ruby fashion, another way of thinking
|
||||
about defining the slice of a string is based on a regex match.
|
||||
|
||||
We can pass a regex and an int (specifying which match we want) to extract some
|
||||
portion of a string based on the regex match. That includes capture groups.
|
||||
|
||||
Here are a couple examples of extracting matching capture groups as well as
|
||||
getting the entire regex match:
|
||||
|
||||
```ruby
|
||||
> "me+abc123@email.com"[/.+\+(.+)@(.+)/, 1]
|
||||
=> "abc123"
|
||||
|
||||
> "me+abc123@email.com"[/.+\+(.+)@(.+)/, 2]
|
||||
=> "email.com"
|
||||
|
||||
> "me+abc123@email.com"[/.+\+(.+)@(.+)/, 0]
|
||||
=> "me+abc123@email.com"
|
||||
|
||||
> "me+abc123@email.com"[/.+\+(.+)@(.+)/]
|
||||
=> "me+abc123@email.com"
|
||||
```
|
||||
|
||||
The `0`th match (which is the default) corresponds to the full match. Each
|
||||
integer position after that corresponds to any capture groups. This maps
|
||||
directly to the underlying `MatchData` object:
|
||||
|
||||
```ruby
|
||||
> /.+\+(.+)@(.+)/.match("me+abc123@email.com")
|
||||
=> #<MatchData "me+abc123@email.com" 1:"abc123" 2:"email.com">
|
||||
```
|
||||
|
||||
[source](https://ruby-doc.org/3.3.6/String.html#class-String-label-String+Slices)
|
||||
54
ruby/install-latest-version-of-ruby-with-asdf.md
Normal file
54
ruby/install-latest-version-of-ruby-with-asdf.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Install Latest Version Of Ruby With asdf
|
||||
|
||||
When I check the `asdf` Ruby plugin for known versions of Ruby:
|
||||
|
||||
```bash
|
||||
$ asdf list-all ruby | fzf
|
||||
```
|
||||
|
||||
I don't find the latest (`3.4`).
|
||||
|
||||
I need to update the plugin. A newer version of the plugin will know about
|
||||
newer Ruby versions.
|
||||
|
||||
```bash
|
||||
$ asdf plugin-update ruby
|
||||
```
|
||||
|
||||
Now, if I run the `list-all` command again, I'll find the version I'm looking
|
||||
for — `3.4.1`.
|
||||
|
||||
Now that `asdf` and I both know about the version to be installed, I can tell
|
||||
`asdf` to install it:
|
||||
|
||||
```bash
|
||||
$ asdf install ruby 3.4.1
|
||||
```
|
||||
|
||||
Now, if I check the current Ruby version, I'll see that it is still set to some
|
||||
other version.
|
||||
|
||||
```bash
|
||||
$ ruby --version
|
||||
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-darwin22]
|
||||
```
|
||||
|
||||
I need to tell `asdf` to start using this newly installed version instead,
|
||||
either globally or locally.
|
||||
|
||||
```bash
|
||||
$ # globally
|
||||
$ asdf global ruby 3.4.1
|
||||
$ # or locally
|
||||
$ asdf local ruby 3.4.1
|
||||
```
|
||||
|
||||
And now I'm all set:
|
||||
|
||||
```bash
|
||||
$ asdf current ruby
|
||||
ruby 3.4.1 /Users/jbranchaud/.tool-versions
|
||||
|
||||
$ ruby --version
|
||||
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x86_64-darwin22]
|
||||
```
|
||||
43
ruby/refer-to-implicit-block-argument-with-it.md
Normal file
43
ruby/refer-to-implicit-block-argument-with-it.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Refer To Implicit Block Argument With It
|
||||
|
||||
One of the key features of the Ruby 3.4 release is the `it` implicit block
|
||||
argument.
|
||||
|
||||
The vast majority of inline blocks defined in Ruby code receive a single block
|
||||
argument. Typically we name and reference a block argument explictly like so:
|
||||
|
||||
```ruby
|
||||
items.map { |item| item * item }
|
||||
```
|
||||
|
||||
Ruby likes to cut away excess syntax when possible. To that end, the implicit
|
||||
`it` block argument has been added. This is an identifier we can reference in
|
||||
the context of a block and its value is the current
|
||||
|
||||
```ruby
|
||||
items = [1,2,3,4,5]
|
||||
|
||||
squares = items.map { it * it }
|
||||
|
||||
pp squares
|
||||
#=> [1, 4, 9, 16, 25]
|
||||
```
|
||||
|
||||
Note: we cannot mix numbered parameters (`_1`, `_2`) with the `it` parameter.
|
||||
If we do, we'll get the following error:
|
||||
|
||||
```ruby
|
||||
def method_using_block(a, b)
|
||||
yield(a, b) if block_given?
|
||||
end
|
||||
|
||||
puts method_using_block(4,5) { _2 ** _1 } #=> 625
|
||||
puts method_using_block(4,5) { _2 ** it }
|
||||
# it_block.rb:12: syntax error found (SyntaxError)
|
||||
# 10 |
|
||||
# 11 | puts method_using_block(4,5) { _2 ** _1 }
|
||||
# > 12 | ... it }
|
||||
# | ^~ `it` is not allowed when a numbered parameter is already used
|
||||
```
|
||||
|
||||
[source](https://docs.ruby-lang.org/en/3.4/NEWS_md.html)
|
||||
31
sqlite/explore-the-database-schema.md
Normal file
31
sqlite/explore-the-database-schema.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Explore The Database Schema
|
||||
|
||||
The first thing I like to do when connecting to a database is get a quick lay
|
||||
of the land. What are the tables and what do they look like?
|
||||
|
||||
I can list all tables with the `.tables` dot-command.
|
||||
|
||||
```sql
|
||||
sqlite> .tables
|
||||
ingredient_amounts ingredients recipes
|
||||
```
|
||||
|
||||
I can then look at the `create table` statement for specific tables to see what
|
||||
their schema looks like:
|
||||
|
||||
```sql
|
||||
sqlite> .schema recipes
|
||||
CREATE TABLE recipes (
|
||||
id integer primary key,
|
||||
name varchar not null,
|
||||
description text not null,
|
||||
instructions text not null
|
||||
);
|
||||
```
|
||||
|
||||
The `.schema` dot-command can also be used without any argument and it will
|
||||
display the schema for all tables of all connected databases.
|
||||
|
||||
Run `.help` from the `sqlite3` prompt for more dot-command options.
|
||||
|
||||
[source](https://www.sqlite.org/cli.html#querying_the_database_schema)
|
||||
25
unix/count-the-number-of-words-on-a-webpage.md
Normal file
25
unix/count-the-number-of-words-on-a-webpage.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Count The Number Of Words On A Webpage
|
||||
|
||||
I was reading through a couple sections of the `postfix` documentation and I
|
||||
was astounded at how large the webpage is, and that is just for the `main.cf`
|
||||
file format.
|
||||
|
||||
Curiosity got the best of me and I wanted to get a sense of the magnitude of
|
||||
the page. A word count seemed like a good measure.
|
||||
|
||||
Using `pandoc` and a couple other unix utilities, I was able to quickly get
|
||||
that number.
|
||||
|
||||
```bash
|
||||
curl -s http://www.postfix.org/postconf.5.html\#virtual_mailbox_maps | pandoc -f html -t plain | wc -w
|
||||
88383
|
||||
```
|
||||
|
||||
Generically, that is:
|
||||
|
||||
```bash
|
||||
curl -s url | pandoc -f html -t plain | wc -w
|
||||
```
|
||||
|
||||
Pandoc produces a plain-text version of the HTML page that was pulled in by
|
||||
`curl` and then we use `wc` to get a word (`-w`) count.
|
||||
32
unix/get-word-count-for-all-files-in-git-repo.md
Normal file
32
unix/get-word-count-for-all-files-in-git-repo.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Get Word Count For All Files In Git Repo
|
||||
|
||||
As part of gathering numbers for [A Decade of TILs](), I wanted to get an word
|
||||
count of all the TIL markdown files I've committed to this project over its 10
|
||||
year history. By using `git ls-files` with a pattern, I can get a list of all
|
||||
file names. Then with `xargs` I can pass that entire list to `wc -w` which
|
||||
gives a word count of each. The final line that `wc -w` outputs is a sum total
|
||||
of all the file word counts. Lastly, piping that through `tail -n1` gives me
|
||||
just that last total count line.
|
||||
|
||||
```bash
|
||||
$ git ls-files "*/**.md" | xargs wc -w | tail -n1
|
||||
206816 total
|
||||
```
|
||||
|
||||
Since the `tail -n1` obfuscates what the `wc -w` is doing, here is what that
|
||||
looks like before that final pipe.
|
||||
|
||||
```bash
|
||||
$ git ls-files "*/**.md" | tail -n3 | xargs wc -w
|
||||
115 zsh/add-to-the-path-via-path-array.md
|
||||
190 zsh/link-a-scalar-to-an-array.md
|
||||
214 zsh/use-a-space-to-exclude-command-from-history.md
|
||||
519 total
|
||||
```
|
||||
|
||||
I can even clean up the final output a bit more with `awk`:
|
||||
|
||||
```bash
|
||||
$ git ls-files "*/**.md" | xargs wc -w | tail -n1 | awk '{print $1}'
|
||||
206816
|
||||
```
|
||||
27
unix/limit-protocols-used-in-a-curl-command.md
Normal file
27
unix/limit-protocols-used-in-a-curl-command.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Limit Protocols Used In A cURL Command
|
||||
|
||||
I was about to install [`atuin`](https://github.com/atuinsh/atuin). I went to
|
||||
their _Quick Start_ section to grab whatever command I would need to install
|
||||
it. It was a `curl` statement piped to `sh`. The thing that caught my attention
|
||||
though was I `curl` flag that I didn't recognize — `--proto`.
|
||||
|
||||
> Tells curl to limit what protocols it may use for transfers.
|
||||
|
||||
Using `curl --proto '=https' ...` we can enforce that only an `https` URL can
|
||||
be used in this command.
|
||||
|
||||
Here is what happens if I try to run the `atuin`-provided `curl` command after
|
||||
I have downgraded their URL to be `http`:
|
||||
|
||||
```bash
|
||||
curl --proto '=https' --tlsv1.2 -LsSf http://setup.atuin.sh | sh
|
||||
curl: (1) Protocol "http" not supported or disabled in libcurl
|
||||
```
|
||||
|
||||
It doesn't even attempt the request. The protocol is considered unsupported and
|
||||
the command immediately fails.
|
||||
|
||||
In addition to only installing software we trust, we should make sure we are
|
||||
only doing so over a protocol we trust (namely, `https`).
|
||||
|
||||
See `man curl` for more details, including about the modifiers (`=`, `+`, `-`).
|
||||
24
unix/list-all-fonts-on-your-machine.md
Normal file
24
unix/list-all-fonts-on-your-machine.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# List All Fonts On Your Machine
|
||||
|
||||
In trying to figure out what _FiraCode_ font I have installed on my machine
|
||||
and what it is called, I came across [this StackOverflow
|
||||
answer](https://stackoverflow.com/a/52789662/535590) which shares the
|
||||
following one-liner:
|
||||
|
||||
```bash
|
||||
$ fc-list | awk '{$1=""}1' | cut -d: -f1 | sort | uniq
|
||||
```
|
||||
|
||||
This uses `fc-list` to get the names of all the fonts available on your
|
||||
machine. This seems to work on both Linux and Mac. Through a series of `awk`,
|
||||
`cut`, and `sort | uniq`, this command produces a clean, easily-browsed list
|
||||
of fonts.
|
||||
|
||||
I like to take this a step further by piping it all to `fzf` where I can then
|
||||
narrow down the output to just lines that match _FiraCode_.
|
||||
|
||||
```bash
|
||||
$ fc-list | awk '{$1=""}1' | cut -d: -f1 | sort | uniq | fzf
|
||||
```
|
||||
|
||||
See also [`system_profiler SPFontsDataType`](https://apple.stackexchange.com/questions/35852/list-of-activated-fonts-with-shell-command-in-os-x/243746#243746).
|
||||
32
unix/manually-pass-two-git-files-to-delta.md
Normal file
32
unix/manually-pass-two-git-files-to-delta.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Manually Pass Two Git Files To Delta
|
||||
|
||||
I recently [wired up `delta` as my default pager and differ for
|
||||
`git`](git/better-diffs-with-delta.md). However, when I installed `delta`, I
|
||||
first wanted to see what its diff output looked like.
|
||||
|
||||
How can I pass two versions of the same file from `git` to `delta`?
|
||||
|
||||
I can show the current contents of a file with `git show` referencing the
|
||||
`HEAD` commit.
|
||||
|
||||
```bash
|
||||
$ git show HEAD:main.go
|
||||
```
|
||||
|
||||
Similiarly, I can show the contents of that file _one_ commit ago with `HEAD~`.
|
||||
|
||||
```bash
|
||||
$ git show HEAD~:main.go
|
||||
```
|
||||
|
||||
I can then pass each of those commands as virtual files to `delta` using the
|
||||
`<()` syntax. The older file goes first and the newer second.
|
||||
|
||||
```bash
|
||||
$ delta <(git show HEAD~:main.go) <(git show HEAD:main.go)
|
||||
```
|
||||
|
||||
That works and comes in handy if you need to compare two things that aren't
|
||||
necessarily files or aren't necessarily under version control. However, in
|
||||
hindsight, I'd say it is easier to add delta as the pager and differ and try it
|
||||
out directly.
|
||||
29
unix/see-where-asdf-gets-current-tool-version.md
Normal file
29
unix/see-where-asdf-gets-current-tool-version.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# See Where asdf Gets Current Tool Version
|
||||
|
||||
The other day I [installed the latest version of
|
||||
Ruby](ruby/install-latest-version-of-ruby-with-asdf.md) with `asdf`. I then set
|
||||
that version (`3.4.1`) as the global default. However, when I then ran `ruby
|
||||
--version`, I was getting a `3.2.x` version. I checked my current project's
|
||||
directory and there was no `.tool-versions` file, so it wasn't being set by my
|
||||
current directory.
|
||||
|
||||
`asdf` looks up the current chain of directories until it encounters a
|
||||
`.tool-versions` file, so it must have been finding one somewhere up there, but
|
||||
before it was getting to the _global_ `.tool-versions` file. But where?
|
||||
|
||||
The `asdf current` command can tell us for a specific tool what the current
|
||||
version it is set to and what file is giving that directive.
|
||||
|
||||
```bash
|
||||
asdf current ruby
|
||||
ruby 3.2.2 /Users/jbranchaud/code/.tool-versions
|
||||
```
|
||||
|
||||
As it turns out, I had a `.tool-versions` file in `$HOME/code` that was setting
|
||||
that `3.2.x` Ruby version.
|
||||
|
||||
I didn't want that directory controlling the Ruby version, so I removed `ruby`
|
||||
from that file. `asdf` was then able to traverse up to `$HOME/.tool-versions`
|
||||
for the global setting.
|
||||
|
||||
See `asdf help` for more details.
|
||||
48
workflow/break-justfile-into-separate-hidden-steps.md
Normal file
48
workflow/break-justfile-into-separate-hidden-steps.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Break Justfile Into Separate Hidden Steps
|
||||
|
||||
With `just` and a project's `justfile`, I can get a summary of the commands
|
||||
available to run against my project by running `just --list`. If I try to
|
||||
breakdown a complex, multi-step command into separate `just` commands, it will
|
||||
be nice for organization, but it will clutter the list output. I can mark
|
||||
specific commands as hidden or internal by preceding them with an underscore
|
||||
(`_`).
|
||||
|
||||
Here is a `justfile` from one of my projects that only lists a single command
|
||||
`setup` which itself is supported by three internal commands: `_check-brew`,
|
||||
`_install-deps`, and `_install-go-tools`.
|
||||
|
||||
```justfile
|
||||
# Install all required development dependencies
|
||||
setup: _check-brew _install-deps _install-go-tools
|
||||
|
||||
# Check if brew is installed
|
||||
_check-brew:
|
||||
#!/usr/bin/env bash
|
||||
if ! command -v brew &> /dev/null; then
|
||||
echo "Error: Homebrew is not installed"
|
||||
echo "Please install from https://brew.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
brew_deps := '''
|
||||
go
|
||||
sqlite3
|
||||
'''
|
||||
|
||||
# Install brew dependencies
|
||||
_install-deps:
|
||||
#!/usr/bin/env bash
|
||||
deps=$(echo '{{brew_deps}}' | tr -s '[:space:]' ' ' | xargs)
|
||||
for pkg in $deps; do
|
||||
if ! brew list $pkg &>/dev/null; then
|
||||
echo "Installing $pkg..."
|
||||
brew install $pkg
|
||||
else
|
||||
echo "✓ $pkg already installed"
|
||||
fi
|
||||
done
|
||||
|
||||
# Install Go development tools
|
||||
_install-go-tools:
|
||||
go install github.com/pressly/goose/v3/cmd/goose@latest
|
||||
```
|
||||
20
workflow/control-media-with-drop-keyboard.md
Normal file
20
workflow/control-media-with-drop-keyboard.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Control Media With Drop Keyboard
|
||||
|
||||
I have a [Drop CTRL](https://drop.com/buy/drop-ctrl-v2-mechanical-keyboard)
|
||||
mechanical keyboard which mostly works like any other keyboard. It also has a
|
||||
set of functionality that can be accessed via the `fn` (function) key. The
|
||||
function key can be used to configure the keyboard's LEDs, but I tend to set
|
||||
and forget that.
|
||||
|
||||
Instead, I like to use the function key to control media. That is, adjust the
|
||||
volume, play and pause, and skip to the next song.
|
||||
|
||||
Here is a listing of the ones I use:
|
||||
|
||||
- `Fn + Insert` to Pause / Play
|
||||
- `Fn + PgUp/PgDown` to Increase / Decrease the volume
|
||||
- `Fn + Del/End` to go to the Previous / Next song
|
||||
|
||||
Here is a [full listing of the function
|
||||
keys](https://drop.com/talk/9382/how-to-configure-your-drop-keyboard) for Drop
|
||||
keyboards.
|
||||
@@ -13,6 +13,6 @@ $ yarn global add fkill-cli
|
||||
|
||||
Then run it with no arguments to trigger the interactive mode.
|
||||
|
||||

|
||||

|
||||
|
||||
gif credit: [`fkill-cli` repo](https://github.com/sindresorhus/fkill-cli)
|
||||
|
||||
21
workflow/pop-videos-out-as-picture-in-picture.md
Normal file
21
workflow/pop-videos-out-as-picture-in-picture.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Pop Videos Out As Picture-in-Picture
|
||||
|
||||
I recently learned that just about any video playing in Chrome and Firefox can
|
||||
be popped out to a picture-in-picture (PIP) player. A PIP player gives you a sidecar
|
||||
video player window that you can arrange and resize anywhere on your screen. It
|
||||
sits on top of other windows so that you can view it while working from other
|
||||
apps. This is useful if, for instance, you are working through a coding
|
||||
tutorial on youtube.
|
||||
|
||||
For most video players, you can right click on the video and the menu that
|
||||
appears will include a "Picture in Picture" option. Select that and the arrange
|
||||
the player to your liking.
|
||||
|
||||
Youtube overrides right-click. If you right-click, you'll see one menu of
|
||||
Youtube-specific options. Right-click a second time to open the standard
|
||||
browser menu which will include the PIP option.
|
||||
|
||||
I noticed while also testing this on Firefox, that they have a PIP icon that
|
||||
appears as a small overlay on the right side of the video player that you can
|
||||
click as well. This is useful because I found some site's video players were
|
||||
(inadvertently) preventing right-click.
|
||||
31
workflow/send-a-pdf-to-your-kindle.md
Normal file
31
workflow/send-a-pdf-to-your-kindle.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# Send A PDF To Your Kindle
|
||||
|
||||
I recently got a Kindle. I already have a bunch of PDF and ePub books on my
|
||||
computer that I've bought over the years. I wanted to be able to read some of
|
||||
those books on the Kindle. I found that there is a way to send these formats to
|
||||
your Kindle via email.
|
||||
|
||||
There are a couple steps to get this working.
|
||||
|
||||
First, from the Amazon account that is tied to the Kindle device, open the
|
||||
_Account_ dropdown and click _Devices. Any devices tied to your account will be
|
||||
listed there. Navigate to the one you want to send to. Under the _Device
|
||||
Summary_ with be a custom email address for that device. Something like
|
||||
`youremail_abc123@kindle.com`.
|
||||
|
||||
That's the email you'll send the PDF or ePub attachment to.
|
||||
|
||||
Second, that Kindle email address will only receive and process documents from
|
||||
a known, verified email address. Back on the _Devices_page, click on the
|
||||
_Preferences_ tab. Under _Personal Document Preferences_ make sure that the
|
||||
_Approved Personal Document Email List_ includes the email address you'll be
|
||||
sending from. Add it if not.
|
||||
|
||||
Everything is set up. Now compose an email to that Kindle address, add the
|
||||
attachment, and send. Give it 5 or so minutes to process and it should show up
|
||||
on your device.
|
||||
|
||||
Additionally, you can go to the _Content_ tab and then to _Digital Content_ to
|
||||
see what documents you have set and which devices have received them.
|
||||
|
||||
[source](https://goodereader.com/blog/kindle/here-is-how-you-can-read-pdf-files-on-the-amazon-kindle)
|
||||
22
workflow/show-linting-errors-in-zed.md
Normal file
22
workflow/show-linting-errors-in-zed.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Show Linting Errors In Zed
|
||||
|
||||
When working in a language like TypeScript or Go, the language server tooling
|
||||
in [Zed](https://zed.dev/) can draw my attention to errors in my code. This
|
||||
could be an unrecognized function or variable, a type error, or a syntax error.
|
||||
When these linting errors are detected, the editor underlines them with a red
|
||||
squiggly. I can hover over offending token or statement and see what the error
|
||||
is.
|
||||
|
||||
There are also a few mouse-free ways to do this.
|
||||
|
||||
First, I can hit `F8` to jump to the next one of these errors in the current
|
||||
file. That will move my cursor to that location and display a small overlay
|
||||
with the error details.
|
||||
|
||||
Second, assuming Vim mode, I can navigate my cursor over a specific highlighted
|
||||
token and then hit `Shift+k`. That will pop open the same small overlay to
|
||||
display the error details.
|
||||
|
||||
Third, I can hit `Cmd+Shift+M` to open the _Project Diagnostics_ tab which
|
||||
displays a series of file buffer results with the offending lines and the error
|
||||
description.
|
||||
14
workflow/temporarily-hide-cleanshot-x-capture-previews.md
Normal file
14
workflow/temporarily-hide-cleanshot-x-capture-previews.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Temporarily Hide CleanShot X Capture Previews
|
||||
|
||||
The _capture previews_ that CleanShot X provides are a useful part of my
|
||||
workflow. I often capture a screenshot or recording a bit in advance of needing
|
||||
to add it as, say, an attachment. The preview icons float off to the right of
|
||||
my screen, generally out of the way. As soon as I need them, I can annotate,
|
||||
drag-n-drop, etc.
|
||||
|
||||
Sometimes, however, they do get in the way. But I'm not ready to dismiss them
|
||||
and I don't want to save them off to some folder buried in my file system.
|
||||
|
||||
To temporarily slide the capture previews down off the screen, I can hit the
|
||||
shortcut `Ctrl+Opt+Cmd+H`. The arrow at the butto of the screen can be clicked
|
||||
to unhide them, or I can hit the same shortcut sequence to reveal them.
|
||||
Reference in New Issue
Block a user