mirror of
https://github.com/jbranchaud/til
synced 2026-01-19 15:08:02 +00:00
Compare commits
20 Commits
0e41f64edc
...
e996a2fcfb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e996a2fcfb | ||
|
|
77f3c6a43d | ||
|
|
bf6a10e2cd | ||
|
|
5083c8e9f1 | ||
|
|
a4c67c33a3 | ||
|
|
dce54bd689 | ||
|
|
f1cc33fe40 | ||
|
|
9dcd9daf0a | ||
|
|
9afe6503ec | ||
|
|
b376f32a67 | ||
|
|
3abfa92b64 | ||
|
|
d086d3b943 | ||
|
|
f64257f02c | ||
|
|
b329d36888 | ||
|
|
e0db60f6ce | ||
|
|
5c81ddc151 | ||
|
|
6af86bd407 | ||
|
|
98d8249cf1 | ||
|
|
7f1c243310 | ||
|
|
cff6592c4e |
23
README.md
23
README.md
@@ -10,7 +10,7 @@ pairing with smart people at Hashrocket.
|
|||||||
|
|
||||||
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
|
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
|
||||||
|
|
||||||
_1514 TILs and counting..._
|
_1531 TILs and counting..._
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ _1514 TILs and counting..._
|
|||||||
* [Internet](#internet)
|
* [Internet](#internet)
|
||||||
* [Java](#java)
|
* [Java](#java)
|
||||||
* [JavaScript](#javascript)
|
* [JavaScript](#javascript)
|
||||||
|
* [jj](#jj)
|
||||||
* [jq](#jq)
|
* [jq](#jq)
|
||||||
* [Kitty](#kitty)
|
* [Kitty](#kitty)
|
||||||
* [Linux](#linux)
|
* [Linux](#linux)
|
||||||
@@ -310,6 +311,7 @@ _1514 TILs and counting..._
|
|||||||
- [Find And Remove Files That Match A Name](git/find-and-remove-files-that-match-a-name.md)
|
- [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 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)
|
- [Find The Initial Commit](git/find-the-initial-commit.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 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)
|
- [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)
|
- [Grab A Single File From A Stash](git/grab-a-single-file-from-a-stash.md)
|
||||||
@@ -397,9 +399,12 @@ _1514 TILs and counting..._
|
|||||||
|
|
||||||
- [Access Go Docs Offline](go/access-go-docs-offline.md)
|
- [Access Go Docs Offline](go/access-go-docs-offline.md)
|
||||||
- [Build For A Specific OS And Architecture](go/build-for-a-specific-os-and-architecture.md)
|
- [Build For A Specific OS And Architecture](go/build-for-a-specific-os-and-architecture.md)
|
||||||
|
- [Combine Two Slices](go/combine-two-slices.md)
|
||||||
- [Do Something N Times](go/do-something-n-times.md)
|
- [Do Something N Times](go/do-something-n-times.md)
|
||||||
- [Find Executables Installed By Go](go/find-executables-installed-by-go.md)
|
- [Find Executables Installed By Go](go/find-executables-installed-by-go.md)
|
||||||
- [Not So Random](go/not-so-random.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)
|
||||||
- [Replace The Current Process With An External Command](go/replace-the-current-process-with-an-external-command.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)
|
- [Sleep For A Duration](go/sleep-for-a-duration.md)
|
||||||
- [Upgrading From An Older Version On Mac](go/upgrading-from-an-older-version-on-mac.md)
|
- [Upgrading From An Older Version On Mac](go/upgrading-from-an-older-version-on-mac.md)
|
||||||
@@ -455,6 +460,7 @@ _1514 TILs and counting..._
|
|||||||
- [Get Random Images From Unsplash](internet/get-random-images-from-unsplash.md)
|
- [Get Random Images From Unsplash](internet/get-random-images-from-unsplash.md)
|
||||||
- [Search Tweets By Author](internet/search-tweets-by-author.md)
|
- [Search Tweets By Author](internet/search-tweets-by-author.md)
|
||||||
- [Show All Pivotal Stories With Blockers](internet/show-all-pivotal-stories-with-blockers.md)
|
- [Show All Pivotal Stories With Blockers](internet/show-all-pivotal-stories-with-blockers.md)
|
||||||
|
- [Verify Site Ownership With DNS Record](internet/verify-site-ownership-with-dns-record.md)
|
||||||
|
|
||||||
### Java
|
### Java
|
||||||
|
|
||||||
@@ -568,6 +574,11 @@ _1514 TILs and counting..._
|
|||||||
- [Yarn Commands Without The Emojis](javascript/yarn-commands-without-the-emojis.md)
|
- [Yarn Commands Without The Emojis](javascript/yarn-commands-without-the-emojis.md)
|
||||||
- [Yup Schemas Are Validated Asynchronously](javascript/yup-schemas-are-validated-asynchronously.md)
|
- [Yup Schemas Are Validated Asynchronously](javascript/yup-schemas-are-validated-asynchronously.md)
|
||||||
|
|
||||||
|
### jj
|
||||||
|
|
||||||
|
- [Colocate jj And git Directories For Project](jj/colocate-jj-and-git-directories-for-project.md)
|
||||||
|
- [Find System-wide Config File For User](jj/find-system-wide-config-file-for-user.md)
|
||||||
|
|
||||||
### jq
|
### jq
|
||||||
|
|
||||||
- [Combine An Array Of Objects Into A Single Object](jq/combine-an-array-of-objects-into-a-single-object.md)
|
- [Combine An Array Of Objects Into A Single Object](jq/combine-an-array-of-objects-into-a-single-object.md)
|
||||||
@@ -630,6 +641,7 @@ _1514 TILs and counting..._
|
|||||||
- [Specify App When Opening From Command Line](mac/specify-app-when-opening-from-command-line.md)
|
- [Specify App When Opening From Command Line](mac/specify-app-when-opening-from-command-line.md)
|
||||||
- [Use Default Screenshot Shortcuts With CleanShot X](mac/use-default-screenshot-shortcuts-with-cleanshot-x.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)
|
- [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)
|
||||||
|
|
||||||
### MongoDB
|
### MongoDB
|
||||||
|
|
||||||
@@ -680,6 +692,7 @@ _1514 TILs and counting..._
|
|||||||
- [Fetch Does Not Work In API Serverless Function](nextjs/fetch-does-not-work-in-api-serverless-function.md)
|
- [Fetch Does Not Work In API Serverless Function](nextjs/fetch-does-not-work-in-api-serverless-function.md)
|
||||||
- [Make Environment Variable Publicly Available](nextjs/make-environment-variable-publicly-available.md)
|
- [Make Environment Variable Publicly Available](nextjs/make-environment-variable-publicly-available.md)
|
||||||
- [Match Middleware On Groups Of Paths](nextjs/match-middleware-on-groups-of-paths.md)
|
- [Match Middleware On Groups Of Paths](nextjs/match-middleware-on-groups-of-paths.md)
|
||||||
|
- [Organize Pages In Route Groups](nextjs/organize-pages-in-route-groups.md)
|
||||||
- [Precedence Of Dot Env Files](nextjs/precedence-of-dot-env-files.md)
|
- [Precedence Of Dot Env Files](nextjs/precedence-of-dot-env-files.md)
|
||||||
- [Push A Route With A URL Object](nextjs/push-a-route-with-a-url-object.md)
|
- [Push A Route With A URL Object](nextjs/push-a-route-with-a-url-object.md)
|
||||||
- [Redirect An Unauthorized User](nextjs/redirect-an-unauthorized-user.md)
|
- [Redirect An Unauthorized User](nextjs/redirect-an-unauthorized-user.md)
|
||||||
@@ -767,6 +780,7 @@ _1514 TILs and counting..._
|
|||||||
- [Escaping String Literals With Dollar Quoting](postgres/escaping-string-literals-with-dollar-quoting.md)
|
- [Escaping String Literals With Dollar Quoting](postgres/escaping-string-literals-with-dollar-quoting.md)
|
||||||
- [Export Query Results To A CSV](postgres/export-query-results-to-a-csv.md)
|
- [Export Query Results To A CSV](postgres/export-query-results-to-a-csv.md)
|
||||||
- [Extracting Nested JSON Data](postgres/extracting-nested-json-data.md)
|
- [Extracting Nested JSON Data](postgres/extracting-nested-json-data.md)
|
||||||
|
- [Fetch Specific Number Of Results](postgres/fetch-specific-number-of-results.md)
|
||||||
- [Find Duplicate Records In Table Without Unique Id](postgres/find-duplicate-records-in-table-without-unique-id.md)
|
- [Find Duplicate Records In Table Without Unique Id](postgres/find-duplicate-records-in-table-without-unique-id.md)
|
||||||
- [Find Records That Contain Duplicate Values](postgres/find-records-that-contain-duplicate-values.md)
|
- [Find Records That Contain Duplicate Values](postgres/find-records-that-contain-duplicate-values.md)
|
||||||
- [Find Records That Have Multiple Associated Records](postgres/find-records-that-have-multiple-associated-records.md)
|
- [Find Records That Have Multiple Associated Records](postgres/find-records-that-have-multiple-associated-records.md)
|
||||||
@@ -815,6 +829,7 @@ _1514 TILs and counting..._
|
|||||||
- [Manage Major Versions With Brew And Direnv](postgres/manage-major-versions-with-brew-and-direnv.md)
|
- [Manage Major Versions With Brew And Direnv](postgres/manage-major-versions-with-brew-and-direnv.md)
|
||||||
- [Max Identifier Length Is 63 Bytes](postgres/max-identifier-length-is-63-bytes.md)
|
- [Max Identifier Length Is 63 Bytes](postgres/max-identifier-length-is-63-bytes.md)
|
||||||
- [Open Heroku Database In Postico From Terminal](postgres/open-heroku-database-in-postico-from-terminal.md)
|
- [Open Heroku Database In Postico From Terminal](postgres/open-heroku-database-in-postico-from-terminal.md)
|
||||||
|
- [Output Explain Query Plan In Different Formats](postgres/output-explain-query-plan-in-different-formats.md)
|
||||||
- [pg Prefix Is Reserved For System Schemas](postgres/pg-prefix-is-reserved-for-system-schemas.md)
|
- [pg Prefix Is Reserved For System Schemas](postgres/pg-prefix-is-reserved-for-system-schemas.md)
|
||||||
- [Postgres Does Not Support Unsigned Integers](postgres/postgres-does-not-support-unsigned-integers.md)
|
- [Postgres Does Not Support Unsigned Integers](postgres/postgres-does-not-support-unsigned-integers.md)
|
||||||
- [Prepare, Execute, And Deallocate Statements](postgres/prepare-execute-and-deallocate-statements.md)
|
- [Prepare, Execute, And Deallocate Statements](postgres/prepare-execute-and-deallocate-statements.md)
|
||||||
@@ -1182,6 +1197,7 @@ _1514 TILs and counting..._
|
|||||||
- [Audit Your Ruby Project For Any CVEs](ruby/audit-your-ruby-project-for-any-cves.md)
|
- [Audit Your Ruby Project For Any CVEs](ruby/audit-your-ruby-project-for-any-cves.md)
|
||||||
- [Assoc For Hashes](ruby/assoc-for-hashes.md)
|
- [Assoc For Hashes](ruby/assoc-for-hashes.md)
|
||||||
- [Block Comments](ruby/block-comments.md)
|
- [Block Comments](ruby/block-comments.md)
|
||||||
|
- [Block Syntaxes Have Different Precedence](ruby/block-syntaxes-have-different-precedence.md)
|
||||||
- [Build HTTP And HTTPS URLs](ruby/build-http-and-https-urls.md)
|
- [Build HTTP And HTTPS URLs](ruby/build-http-and-https-urls.md)
|
||||||
- [Chaining Multiple RSpec Change Matchers](ruby/chaining-multiple-rspec-change-matchers.md)
|
- [Chaining Multiple RSpec Change Matchers](ruby/chaining-multiple-rspec-change-matchers.md)
|
||||||
- [Check For Any Overlaps In List Of Ranges](ruby/check-for-any-overlaps-in-list-of-ranges.md)
|
- [Check For Any Overlaps In List Of Ranges](ruby/check-for-any-overlaps-in-list-of-ranges.md)
|
||||||
@@ -1225,6 +1241,7 @@ _1514 TILs and counting..._
|
|||||||
- [Finding The Source of Ruby Methods](ruby/finding-the-source-of-ruby-methods.md)
|
- [Finding The Source of Ruby Methods](ruby/finding-the-source-of-ruby-methods.md)
|
||||||
- [Format A Hash Into A String Template](ruby/format-a-hash-into-a-string-template.md)
|
- [Format A Hash Into A String Template](ruby/format-a-hash-into-a-string-template.md)
|
||||||
- [Forward All Arguments To Another Method](ruby/forward-all-arguments-to-another-method.md)
|
- [Forward All Arguments To Another Method](ruby/forward-all-arguments-to-another-method.md)
|
||||||
|
- [Gather Positional Arguments In Method Definition](ruby/gather-positional-arguments-in-method-definition.md)
|
||||||
- [Generate A Signed JWT Token](ruby/generate-a-signed-jwt-token.md)
|
- [Generate A Signed JWT Token](ruby/generate-a-signed-jwt-token.md)
|
||||||
- [Generate Ruby Version And Gemset Files With RVM](ruby/generate-ruby-version-and-gemset-files-with-rvm.md)
|
- [Generate Ruby Version And Gemset Files With RVM](ruby/generate-ruby-version-and-gemset-files-with-rvm.md)
|
||||||
- [Get Info About Your RubyGems Environment](ruby/get-info-about-your-ruby-gems-environment.md)
|
- [Get Info About Your RubyGems Environment](ruby/get-info-about-your-ruby-gems-environment.md)
|
||||||
@@ -1292,6 +1309,7 @@ _1514 TILs and counting..._
|
|||||||
- [Specify How Random Array#sample Is](ruby/specify-how-random-array-sample-is.md)
|
- [Specify How Random Array#sample Is](ruby/specify-how-random-array-sample-is.md)
|
||||||
- [Split A Float Into Its Integer And Decimal](ruby/split-a-float-into-its-integer-and-decimal.md)
|
- [Split A Float Into Its Integer And Decimal](ruby/split-a-float-into-its-integer-and-decimal.md)
|
||||||
- [Squeeze Out The Extra Space](ruby/squeeze-out-the-extra-space.md)
|
- [Squeeze Out The Extra Space](ruby/squeeze-out-the-extra-space.md)
|
||||||
|
- [Stack Heredocs In A Method Call](ruby/stack-heredocs-in-a-method-call.md)
|
||||||
- [String Interpolation With Instance Variables](ruby/string-interpolation-with-instance-variables.md)
|
- [String Interpolation With Instance Variables](ruby/string-interpolation-with-instance-variables.md)
|
||||||
- [Summing Collections](ruby/summing-collections.md)
|
- [Summing Collections](ruby/summing-collections.md)
|
||||||
- [Triple Equals: The Case Equality Operator](ruby/triple-equals-the-case-equality-operator.md)
|
- [Triple Equals: The Case Equality Operator](ruby/triple-equals-the-case-equality-operator.md)
|
||||||
@@ -1495,6 +1513,7 @@ _1514 TILs and counting..._
|
|||||||
- [List Stats For A File](unix/list-stats-for-a-file.md)
|
- [List Stats For A File](unix/list-stats-for-a-file.md)
|
||||||
- [List The Available JDKs](unix/list-the-available-jdks.md)
|
- [List The Available JDKs](unix/list-the-available-jdks.md)
|
||||||
- [List The Stack Of Remembered Directories](unix/list-the-stack-of-remembered-directories.md)
|
- [List The Stack Of Remembered Directories](unix/list-the-stack-of-remembered-directories.md)
|
||||||
|
- [List TXT DNS Records For A Domain](unix/list-txt-dns-records-for-a-domain.md)
|
||||||
- [Load Env Vars In Bash Script](unix/load-env-vars-in-bash-script.md)
|
- [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)
|
- [Look Through All Files That Have Been Git Stashed](unix/look-through-all-files-that-have-been-git-stashed.md)
|
||||||
- [Make Direnv Less Noisy](unix/make-direnv-less-noisy.md)
|
- [Make Direnv Less Noisy](unix/make-direnv-less-noisy.md)
|
||||||
@@ -1537,6 +1556,7 @@ _1514 TILs and counting..._
|
|||||||
- [Switch Versions of a Brew Formula](unix/switch-versions-of-a-brew-formula.md)
|
- [Switch Versions of a Brew Formula](unix/switch-versions-of-a-brew-formula.md)
|
||||||
- [Tell direnv To Load The Env File](unix/tell-direnv-to-load-the-env-file.md)
|
- [Tell direnv To Load The Env File](unix/tell-direnv-to-load-the-env-file.md)
|
||||||
- [Touch Access And Modify Times Individually](unix/touch-access-and-modify-times-individually.md)
|
- [Touch Access And Modify Times Individually](unix/touch-access-and-modify-times-individually.md)
|
||||||
|
- [Type Fewer Paths With Brace Expansion](unix/type-fewer-paths-with-brace-expansion.md)
|
||||||
- [Undo Changes Made To Current Terminal Prompt](unix/undo-changes-made-to-current-terminal-prompt.md)
|
- [Undo Changes Made To Current Terminal Prompt](unix/undo-changes-made-to-current-terminal-prompt.md)
|
||||||
- [Undo Some Command Line Editing](unix/undo-some-command-line-editing.md)
|
- [Undo Some Command Line Editing](unix/undo-some-command-line-editing.md)
|
||||||
- [Unrestrict Where ripgrep Searches](unix/unrestrict-where-ripgrep-searches.md)
|
- [Unrestrict Where ripgrep Searches](unix/unrestrict-where-ripgrep-searches.md)
|
||||||
@@ -1728,6 +1748,7 @@ _1514 TILs and counting..._
|
|||||||
- [Open An Integrated Terminal Window](vscode/open-an-integrated-terminal-window.md)
|
- [Open An Integrated Terminal Window](vscode/open-an-integrated-terminal-window.md)
|
||||||
- [Pop Open The Quick Fix Window](vscode/pop-open-the-quick-fix-window.md)
|
- [Pop Open The Quick Fix Window](vscode/pop-open-the-quick-fix-window.md)
|
||||||
- [Step Through Project-Wide Search Results](vscode/step-through-project-wide-search-results.md)
|
- [Step Through Project-Wide Search Results](vscode/step-through-project-wide-search-results.md)
|
||||||
|
- [Synchronize Vim Clipboard With System Clipboard](vscode/synchronize-vim-clipboard-with-system-clipboard.md)
|
||||||
- [Toggle Between Terminals](vscode/toggle-between-terminals.md)
|
- [Toggle Between Terminals](vscode/toggle-between-terminals.md)
|
||||||
- [Turn Off Display Of Tabs For Files](vscode/turn-off-display-of-tabs-for-files.md)
|
- [Turn Off Display Of Tabs For Files](vscode/turn-off-display-of-tabs-for-files.md)
|
||||||
|
|
||||||
|
|||||||
25
git/get-latest-commit-timestamp-for-a-file.md
Normal file
25
git/get-latest-commit-timestamp-for-a-file.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Get Latest Commit Timestamp For A File
|
||||||
|
|
||||||
|
The `git log` command can tell you all the commits that touched a file. That
|
||||||
|
can be narrowed down to the latest commit for that file with the `-1` flag. The
|
||||||
|
commit that it reports can then be further formatted to with the `--format`
|
||||||
|
flag.
|
||||||
|
|
||||||
|
The `%ai` format pattern gives the date the commit was authored in an ISO
|
||||||
|
8601-like format. The `%aI` (capital `I`) gives the date the commit was
|
||||||
|
authored strictly in the ISO 8601 format.
|
||||||
|
|
||||||
|
Here are examples of both side by side:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
❯ git log -1 --format=%ai -- README.md
|
||||||
|
2024-10-15 13:59:09 -0500
|
||||||
|
|
||||||
|
❯ git log -1 --format=%aI -- README.md
|
||||||
|
2024-10-15T13:59:09-05:00
|
||||||
|
```
|
||||||
|
|
||||||
|
I made use of this in a script where I needed to get an idea of when various
|
||||||
|
files were most recently modified.
|
||||||
|
|
||||||
|
See `man git-log` and the `PRETTY FORMATS` section for more details.
|
||||||
51
go/combine-two-slices.md
Normal file
51
go/combine-two-slices.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Combine Two Slices
|
||||||
|
|
||||||
|
The `append` function can be used to create a new slice with the contents of
|
||||||
|
the given slice and one or more items added to the end.
|
||||||
|
|
||||||
|
We can add one or more items like so:
|
||||||
|
|
||||||
|
```go
|
||||||
|
s1 := []int{1, 2, 3, 4}
|
||||||
|
s2 := append(s1, 5)
|
||||||
|
s3 := append(s2, 6, 7, 8)
|
||||||
|
|
||||||
|
fmt.Println(s1) //=> [1 2 3 4]
|
||||||
|
fmt.Println(s2) //=> [1 2 3 4 5]
|
||||||
|
fmt.Println(s3) //=> [1 2 3 4 5 6 7 8]
|
||||||
|
```
|
||||||
|
|
||||||
|
But what if we have a second slice instead of individual items? We could import
|
||||||
|
`slices` and use its `Concat` function. Or we can stick with `append` and
|
||||||
|
unpack that slice as a series of arguments into the second part of `append`
|
||||||
|
using `slice...`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
s4 := append(s2, s1...)
|
||||||
|
fmt.Println(s4) //=> [1 2 3 4 5 1 2 3 4]
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is the full example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
s1 := []int{1, 2, 3, 4}
|
||||||
|
s2 := append(s1, 5)
|
||||||
|
s3 := append(s2, 6, 7, 8)
|
||||||
|
|
||||||
|
fmt.Println(s1)
|
||||||
|
fmt.Println(s2)
|
||||||
|
fmt.Println(s3)
|
||||||
|
|
||||||
|
s4 := append(s2, s1...)
|
||||||
|
fmt.Println(s4)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
[source](https://pkg.go.dev/builtin#append)
|
||||||
39
go/parse-a-string-into-individual-fields.md
Normal file
39
go/parse-a-string-into-individual-fields.md
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
# Parse A String Into Individual Fields
|
||||||
|
|
||||||
|
Let's say you're reading in data from a file or otherwise dealing with an
|
||||||
|
arbitrary string of data. If that string has a series of values separated by
|
||||||
|
whitespace, you can parse it into individual fields with
|
||||||
|
[`strings.Fields`](https://pkg.go.dev/strings#Fields).
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
data := "3 5 2 6 7 1 9"
|
||||||
|
fields := strings.Fields(data)
|
||||||
|
|
||||||
|
fmt.Printf("Fields: %v", fields)
|
||||||
|
// [3 5 2 6 7 1 9]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is another example where we can see that `strings.Fields` deals with
|
||||||
|
multiple whitespace and surrounding whitespace:
|
||||||
|
|
||||||
|
```go
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
data := " go java c++ rust "
|
||||||
|
fields := strings.Fields(data)
|
||||||
|
|
||||||
|
fmt.Printf("%v", fields)
|
||||||
|
// [go java c++ rust]
|
||||||
|
}
|
||||||
|
```
|
||||||
65
go/parse-flags-from-cli-arguments.md
Normal file
65
go/parse-flags-from-cli-arguments.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Parse Flags From CLI Arguments
|
||||||
|
|
||||||
|
Though we can grab the arguments to a Go program from `os.Args`, it requires
|
||||||
|
some manual parsing. With the built-in `flag` package, we can declare specific
|
||||||
|
flags our program accepts, by type. When we parse them, they will be separated
|
||||||
|
out from the rest of the positional arguments.
|
||||||
|
|
||||||
|
Here is an example of the program that accepts a boolean `debug` flag. This
|
||||||
|
will work with either `-debug` or `--debug`.
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
var debug bool
|
||||||
|
flag.BoolVar(&debug, "debug", false, "turns on debug mode, extra logging")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
positionalArgs := flag.Args()
|
||||||
|
|
||||||
|
if len(positionalArgs) < 1 {
|
||||||
|
fmt.Println("Please specify which part to run: 1 or 2")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
if debug {
|
||||||
|
fmt.Println("We are in debug mode...")
|
||||||
|
fmt.Println("Received the following argument:", positionalArgs[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
We can run the program in debug mode like so:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go run . --debug 123
|
||||||
|
We are in debug mode...
|
||||||
|
Received the following argument: 123
|
||||||
|
```
|
||||||
|
|
||||||
|
We can also take advantage of the `help` flag that we get for free:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go run . --help
|
||||||
|
Usage of /var/folders/62/lx9pcjbs1zbd83zg6twwym2r0000gn/T/go-build3212087168/b001/exe/test:
|
||||||
|
-debug
|
||||||
|
turns on debug mode, extra logging
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: any recognized flags need to come before any of the position arguments.
|
||||||
|
The `debug` flag won't be picked up if we run the program like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go run . 123 --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
[source](https://pkg.go.dev/flag)
|
||||||
28
internet/verify-site-ownership-with-dns-record.md
Normal file
28
internet/verify-site-ownership-with-dns-record.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Verify Site Ownership With DNS Record
|
||||||
|
|
||||||
|
To run your site through Google Search Console and get detailed reports, you
|
||||||
|
need to verify that you own the site. There are several manual ways of doing
|
||||||
|
this that involve sticking a value unique to your URL in a file or header tag.
|
||||||
|
There is a better way though.
|
||||||
|
|
||||||
|
By adding a TXT DNS record wherever you domain's DNS is managed, you can prove
|
||||||
|
to Google that you own the domain. That verification applies to all paths and
|
||||||
|
subdomains of that domain.
|
||||||
|
|
||||||
|
Some providers like Cloudflare have a mostly-automated process for this that
|
||||||
|
Google can hook into as long as you grant permission via OAuth.
|
||||||
|
|
||||||
|
You can also manually create the TXT record if necessary.
|
||||||
|
|
||||||
|
Either way, it will look something like:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ dig -t TXT visualmode.dev
|
||||||
|
|
||||||
|
;; ANSWER SECTION:
|
||||||
|
visualmode.dev. 377 IN TXT "google-site-verification=MBZ2S2fhnh2gHRxFniRrYW-O6mdyimJDRFj-f
|
||||||
|
vblwtk"
|
||||||
|
```
|
||||||
|
|
||||||
|
More details are provided in the [Google Search Console
|
||||||
|
docs](https://support.google.com/webmasters/answer/9008080?hl=en#domain_name_verification).
|
||||||
59
jj/colocate-jj-and-git-directories-for-project.md
Normal file
59
jj/colocate-jj-and-git-directories-for-project.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Colocate jj And git Directories For Project
|
||||||
|
|
||||||
|
When doing a standard clone of a git repository with `jj`, you'll get a copy of
|
||||||
|
the project with a `.jj` directory containing the version control information.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ jj git clone git@github.com:jbranchaud/my-repo
|
||||||
|
Fetching into new repo in "/path/of/local/repo"
|
||||||
|
...
|
||||||
|
|
||||||
|
$ exa --tree --all -L 1
|
||||||
|
.
|
||||||
|
├── .gitignore
|
||||||
|
├── .jj
|
||||||
|
├── Cargo.lock
|
||||||
|
├── Cargo.toml
|
||||||
|
└── src
|
||||||
|
```
|
||||||
|
|
||||||
|
This is fine if I'm completely familiar with using
|
||||||
|
[jujutsu](https://martinvonz.github.io/jj/latest/). However, if I'm coming from
|
||||||
|
`git` and still learning, then it would be nice to be able to fallback to
|
||||||
|
familiar `git` commands when needed.
|
||||||
|
|
||||||
|
But without a `.git` directory, I get this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git log
|
||||||
|
fatal: not a git repository (or any of the parent directories): .git
|
||||||
|
```
|
||||||
|
|
||||||
|
When cloning a git repo with `jj`, I can instruct it to _colocate_ which means
|
||||||
|
that it will create both the `.jj` and the `.git` data directories in the
|
||||||
|
project.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ jj git clone --colocate git@github.com:jbranchaud/my-repo
|
||||||
|
Fetching into new repo in "/path/of/local/repo"
|
||||||
|
...
|
||||||
|
|
||||||
|
$ exa --tree --all -L 1
|
||||||
|
.
|
||||||
|
├── .git
|
||||||
|
├── .gitignore
|
||||||
|
├── .jj
|
||||||
|
├── Cargo.lock
|
||||||
|
├── Cargo.toml
|
||||||
|
└── src
|
||||||
|
```
|
||||||
|
|
||||||
|
Now I can run `jj` commands or `git` commands:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ git log
|
||||||
|
commit 0c72abbb83657096677f9a3d5ddc7bce20839165 (HEAD, origin/trunk, trunk)
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
[source](https://martinvonz.github.io/jj/latest/git-compatibility/#co-located-jujutsugit-repos)
|
||||||
25
jj/find-system-wide-config-file-for-user.md
Normal file
25
jj/find-system-wide-config-file-for-user.md
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Find System-wide Config File For User
|
||||||
|
|
||||||
|
The `jj` CLI can be configured in a couple different places. When I recently
|
||||||
|
ran a `jj config` command, I was curious where specifically it was getting set.
|
||||||
|
Those changes didn't appear in the repo's config (`./.jj/repo/config.toml`).
|
||||||
|
That makes sense since it would only apply to that repo. So, where is the
|
||||||
|
system-wide config file?
|
||||||
|
|
||||||
|
The following commond shows where on your machine it is located.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ jj config path --user
|
||||||
|
/Users/jbranchaud/Library/Application Support/jj/config.toml
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, the next time I set a config like this:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ jj config set --user ui.paginate never
|
||||||
|
```
|
||||||
|
|
||||||
|
or want to check what other config options are set to, I can visit that path
|
||||||
|
and take a look.
|
||||||
|
|
||||||
|
[source](https://github.com/martinvonz/jj/blob/main/docs/config.md)
|
||||||
19
mac/write-system-clipboard-to-a-file.md
Normal file
19
mac/write-system-clipboard-to-a-file.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Write System Clipboard To A File
|
||||||
|
|
||||||
|
MacOS has two CLI utilities `pbcopy` and `pbpaste` which, respectively, copy
|
||||||
|
_to_ and paste _from_ the system clipboard via the CLI.
|
||||||
|
|
||||||
|
Let's say I've just copied a large block of text from somewhere onto my system
|
||||||
|
clipboard. I now want to paste that into a new file. Instead of creating a new
|
||||||
|
file, opening it up in my preferred editor, pasting all that text, and saving
|
||||||
|
the file, I can run one small command from the CLI.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ pbpaste > data.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
This redirects the contents of `pbpaste` (which is the system clipboard) into
|
||||||
|
the file `data.txt`. If that file doesn't already exist, then it will be
|
||||||
|
created before the data is written to it.
|
||||||
|
|
||||||
|
See `man pbpaste` for more details.
|
||||||
41
nextjs/organize-pages-in-route-groups.md
Normal file
41
nextjs/organize-pages-in-route-groups.md
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# Organize Pages In Route Groups
|
||||||
|
|
||||||
|
With the Next.js App Router we can organize pages without affecting the URL
|
||||||
|
path structure by nesting those directories and pages within a _Route Group_. A
|
||||||
|
Route Group is directory where the name is surrounded by parentheses, e.g.
|
||||||
|
`/(symbols)`.
|
||||||
|
|
||||||
|
For instance, in my [Ruby Operator
|
||||||
|
Lookup](https://www.visualmode.dev/ruby-operators) project, I have the
|
||||||
|
following structure:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ exa --true src/app/ruby-operators
|
||||||
|
|
||||||
|
src/app/ruby-operators
|
||||||
|
├── (symbols)
|
||||||
|
│ ├── ampersand
|
||||||
|
│ │ └── page.mdx
|
||||||
|
│ ├── arbitrary-keyword-arguments
|
||||||
|
│ │ └── page.mdx
|
||||||
|
│ ├── asterisk
|
||||||
|
│ │ └── page.mdx
|
||||||
|
│ ├── at-symbol
|
||||||
|
│ │ └── page.mdx
|
||||||
|
│ ├── backtick
|
||||||
|
│ │ └── page.mdx
|
||||||
|
│ ├── ...
|
||||||
|
│ └── underscore
|
||||||
|
│ └── page.mdx
|
||||||
|
├── client-layout.tsx
|
||||||
|
├── layout.tsx
|
||||||
|
├── page.tsx
|
||||||
|
└── wrapper.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
I'm able to organize all the different symbols and operators under a separate
|
||||||
|
directory `/(symbol)/`. That makes development easier. However, the end result
|
||||||
|
routing still has each symbol located directly under `/ruby-operators/`, e.g.
|
||||||
|
`/ruby-operators/ampersand`.
|
||||||
|
|
||||||
|
[source](https://nextjs.org/docs/app/getting-started/project-structure#route-groups)
|
||||||
42
postgres/fetch-specific-number-of-results.md
Normal file
42
postgres/fetch-specific-number-of-results.md
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
# Fetch Specific Number Of Results
|
||||||
|
|
||||||
|
If you pull up just about any intro to PostgreSQL (or even SQL), one of the
|
||||||
|
first things they are going to teach you is the `limit` clause. This is taught
|
||||||
|
as _the_ way for limiting the result set to a specific number of rows.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
> select title from books limit 4;
|
||||||
|
+-----------------------+
|
||||||
|
| title |
|
||||||
|
|-----------------------|
|
||||||
|
| The Secret History |
|
||||||
|
| A Gentleman in Moscow |
|
||||||
|
| Exhalation: Stores |
|
||||||
|
| Annihilation |
|
||||||
|
+-----------------------+
|
||||||
|
SELECT 4
|
||||||
|
```
|
||||||
|
|
||||||
|
You might be as surprised as I was to learn that `limit` is not part of the SQL
|
||||||
|
standard. It is extremely common for this use case, but the SQL standard
|
||||||
|
defines `fetch first N rows only` as the way to fetch a specific number of
|
||||||
|
rows. As we can see, [it works identically to `limit
|
||||||
|
N`](https://www.postgresql.org/docs/current/sql-select.html#SQL-LIMIT).
|
||||||
|
|
||||||
|
```sql
|
||||||
|
> select title from books fetch first 4 rows only;
|
||||||
|
+-----------------------+
|
||||||
|
| title |
|
||||||
|
|-----------------------|
|
||||||
|
| The Secret History |
|
||||||
|
| A Gentleman in Moscow |
|
||||||
|
| Exhalation: Stores |
|
||||||
|
| Annihilation |
|
||||||
|
+-----------------------+
|
||||||
|
SELECT 4
|
||||||
|
```
|
||||||
|
|
||||||
|
The `rows` and `row` keywords are interchangeable which makes statements more
|
||||||
|
readable if, for instance, you're doing `... fetch first 1 row only`.
|
||||||
|
|
||||||
|
[source](https://www.cybertec-postgresql.com/en/postgresql-limit-vs-fetch-first-rows-with-ties/)
|
||||||
57
postgres/output-explain-query-plan-in-different-formats.md
Normal file
57
postgres/output-explain-query-plan-in-different-formats.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# Output Explain Query Plan In Different Formats
|
||||||
|
|
||||||
|
The output of an [`explain` (or `explain analyze`) query
|
||||||
|
plan](https://www.postgresql.org/docs/current/sql-explain.html) for a given
|
||||||
|
query defaults to a `TEXT` format that is meant to be read by a person.
|
||||||
|
|
||||||
|
```sql
|
||||||
|
> explain (analyze) select title from books where created_at > now() - '1 year'::interval;
|
||||||
|
QUERY PLAN
|
||||||
|
-------------------------------------------------------------------------------------------------
|
||||||
|
Seq Scan on books (cost=0.00..1.28 rows=5 width=32) (actual time=0.011..0.017 rows=22 loops=1)
|
||||||
|
Filter: (created_at > (now() - '1 year'::interval))
|
||||||
|
Planning Time: 0.052 ms
|
||||||
|
Execution Time: 0.027 ms
|
||||||
|
(4 rows)
|
||||||
|
```
|
||||||
|
|
||||||
|
If we instead want the query plan in a standardized format that is parseable
|
||||||
|
and readable by a program, we can specify an alternate format like `JSON`,
|
||||||
|
`YAML`, or `XML`.
|
||||||
|
|
||||||
|
Here is the same plan with `format json`:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
> explain (analyze, format json) select title from books where created_at > now() - '1 year'::interval;
|
||||||
|
QUERY PLAN
|
||||||
|
----------------------------------------------------------------
|
||||||
|
[ +
|
||||||
|
{ +
|
||||||
|
"Plan": { +
|
||||||
|
"Node Type": "Seq Scan", +
|
||||||
|
"Parallel Aware": false, +
|
||||||
|
"Async Capable": false, +
|
||||||
|
"Relation Name": "books", +
|
||||||
|
"Alias": "books", +
|
||||||
|
"Startup Cost": 0.00, +
|
||||||
|
"Total Cost": 1.28, +
|
||||||
|
"Plan Rows": 5, +
|
||||||
|
"Plan Width": 32, +
|
||||||
|
"Actual Startup Time": 0.008, +
|
||||||
|
"Actual Total Time": 0.014, +
|
||||||
|
"Actual Rows": 22, +
|
||||||
|
"Actual Loops": 1, +
|
||||||
|
"Filter": "(created_at > (now() - '1 year'::interval))",+
|
||||||
|
"Rows Removed by Filter": 0 +
|
||||||
|
}, +
|
||||||
|
"Planning Time": 0.050, +
|
||||||
|
"Triggers": [ +
|
||||||
|
], +
|
||||||
|
"Execution Time": 0.023 +
|
||||||
|
} +
|
||||||
|
]
|
||||||
|
(1 row)
|
||||||
|
```
|
||||||
|
|
||||||
|
I present all four formats for a complex query plan [in this
|
||||||
|
Gist](https://gist.github.com/jbranchaud/731b1a68f5cc70c4f7a9e1f5ef570836).
|
||||||
29
ruby/block-syntaxes-have-different-precedence.md
Normal file
29
ruby/block-syntaxes-have-different-precedence.md
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
# Block Syntaxes Have Different Precedence
|
||||||
|
|
||||||
|
There are two syntaxes for defining a block in Ruby. The semantically shorthand
|
||||||
|
syntax uses the curly braces (`{}`). The semantically multi-line syntax uses
|
||||||
|
`do` and `end`. For nearly all intents and purposes they are interchangable.
|
||||||
|
|
||||||
|
It is, however, worth noting that the `do`/`end` version has a lower precedence
|
||||||
|
than the already low precedence of `{}`. That said, you have to write some
|
||||||
|
weird code for this to become an issue.
|
||||||
|
|
||||||
|
Let's say we have two methods, `method_one` and `method_two`. They are both
|
||||||
|
called on the same line like below and then followed by a block argument. Which
|
||||||
|
method receives the block argument?
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
method_one method_two { |n|
|
||||||
|
puts "Executing a block: #{n}"
|
||||||
|
}
|
||||||
|
|
||||||
|
method_one method_two do |n|
|
||||||
|
puts "Executing a block: #{n}"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
In the first case, with the curly braces, `method_two` receives the block as an
|
||||||
|
argument. In the second case, with the `do`/`end`, `method_one` receives the
|
||||||
|
block as an argument.
|
||||||
|
|
||||||
|
[source](http://localhost:3131/ruby-operators/curly-braces#block-shorthand)
|
||||||
@@ -21,5 +21,7 @@ at the same time when you call
|
|||||||
|
|
||||||
```ruby
|
```ruby
|
||||||
list = [3,7,4,15,9,1,2]
|
list = [3,7,4,15,9,1,2]
|
||||||
|
|
||||||
|
list.minmax
|
||||||
#=> [1,15]
|
#=> [1,15]
|
||||||
```
|
```
|
||||||
|
|||||||
55
ruby/gather-positional-arguments-in-method-definition.md
Normal file
55
ruby/gather-positional-arguments-in-method-definition.md
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
# Gather Positional Arguments In Method Definition
|
||||||
|
|
||||||
|
The `*` symbol can be used in Ruby in a method definition to gather up an
|
||||||
|
arbitrary number of positional arguments.
|
||||||
|
|
||||||
|
For instance, we can gather all positional arguments with this method
|
||||||
|
definition:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
def gather_all(*args)
|
||||||
|
puts args
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Or we can isolate the first positional arg and then gather the rest:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
def first_and_rest(first, *rest)
|
||||||
|
puts "First: #{first}, Rest: #{rest}"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
We can even do something a bit more interesting like isolating the first and
|
||||||
|
last arguments while gathering up everything else in the middle:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
def pop_parens(left, *middle, right)
|
||||||
|
if left != '(' || right != ')'
|
||||||
|
raise "Uh oh!"
|
||||||
|
else
|
||||||
|
if middle.size == 1
|
||||||
|
puts "Found: #{middle.first}"
|
||||||
|
else
|
||||||
|
pop_parens(*middle)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Here is what it looks like if we splat some different sets of arguments into
|
||||||
|
that method call:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
> tokens1 = "((((4))))".split('')
|
||||||
|
=> ["(", "(", "(", "(", "4", ")", ")", ")", ")"]
|
||||||
|
> tokens2 = "((4))))".split('')
|
||||||
|
=> ["(", "(", "4", ")", ")", ")", ")"]
|
||||||
|
> pop_parens(*tokens1)
|
||||||
|
Found: 4
|
||||||
|
=> nil
|
||||||
|
> pop_parens(*tokens2)
|
||||||
|
(irb):87:in `pop_parens': Uh oh! (RuntimeError)
|
||||||
|
```
|
||||||
|
|
||||||
|
[source](https://ruby-doc.org/3.3.6/syntax/methods_rdoc.html#label-Array-2FHash+Argument)
|
||||||
37
ruby/stack-heredocs-in-a-method-call.md
Normal file
37
ruby/stack-heredocs-in-a-method-call.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Stack Heredocs In A Method Call
|
||||||
|
|
||||||
|
When you put a heredoc directly in a method call as an argument, it is only the
|
||||||
|
opening identifier that goes in the argument list.
|
||||||
|
|
||||||
|
That looks like this:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
execute_in_transaction(<<~SQL)
|
||||||
|
update reading_statuses
|
||||||
|
set status = 'abandoned'
|
||||||
|
where started_at < (now() - '2 years'::interval)
|
||||||
|
and finished_at is null;
|
||||||
|
SQL
|
||||||
|
```
|
||||||
|
|
||||||
|
You might imagine then that we can put multiple heredocs in a method call. That
|
||||||
|
leads to [_stacked
|
||||||
|
heredocs_](https://www.visualmode.dev/ruby-operators/heredoc#stacked-heredocs).
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
execute_in_transaction(<<~SQL1, <<~SQL2, <<~SQL3)
|
||||||
|
update reading_statuses
|
||||||
|
set status = 'abandoned'
|
||||||
|
where started_at < (now() - '2 years'::interval)
|
||||||
|
and finished_at is null;
|
||||||
|
SQL1
|
||||||
|
insert into activity_log (name, description)
|
||||||
|
values ('abandon_books', 'Mark unread books as abandoned');
|
||||||
|
SQL2
|
||||||
|
delete from background_jobs
|
||||||
|
where id = #{job_id}; -- better to sanitize values like this
|
||||||
|
SQL3
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice we terminate the body of each heredoc with its closing identifier and
|
||||||
|
immediately begin the body of the next one.
|
||||||
43
unix/list-txt-dns-records-for-a-domain.md
Normal file
43
unix/list-txt-dns-records-for-a-domain.md
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
# List TXT DNS Records For A Domain
|
||||||
|
|
||||||
|
The `dig` command can be used to list specifically the `TXT` DNS records for a
|
||||||
|
domain using the `-t TXT` flag like so:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ dig -t TXT visualmode.dev
|
||||||
|
|
||||||
|
; <<>> DiG 9.10.6 <<>> -t TXT visualmode.dev
|
||||||
|
;; global options: +cmd
|
||||||
|
;; Got answer:
|
||||||
|
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 41226
|
||||||
|
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
|
||||||
|
|
||||||
|
;; OPT PSEUDOSECTION:
|
||||||
|
; EDNS: version: 0, flags:; udp: 4096
|
||||||
|
;; QUESTION SECTION:
|
||||||
|
;visualmode.dev. IN TXT
|
||||||
|
|
||||||
|
;; ANSWER SECTION:
|
||||||
|
visualmode.dev. 377 IN TXT "v=spf1 include:_spf.mx.cloudflare.net ~all"
|
||||||
|
visualmode.dev. 377 IN TXT "google-site-verification=MBZ2S2fhnh2gHRxFniRrYW-O6mdyimJDRFj-f
|
||||||
|
vblwtk"
|
||||||
|
|
||||||
|
;; Query time: 103 msec
|
||||||
|
;; SERVER: fe80::7c4b:26ff:fe85:e164%6#53(fe80::7c4b:26ff:fe85:e164%6)
|
||||||
|
;; WHEN: Tue Dec 03 12:49:38 CST 2024
|
||||||
|
;; MSG SIZE rcvd: 179
|
||||||
|
```
|
||||||
|
|
||||||
|
This is still rather verbose though. With the `+short` option we can pare down
|
||||||
|
the output to the values of any TXT records and nothing else.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ dig -t TXT visualmode.dev +short
|
||||||
|
"v=spf1 include:_spf.mx.cloudflare.net ~all"
|
||||||
|
"google-site-verification=MBZ2S2fhnh2gHRxFniRrYW-O6mdyimJDRFj-fvblwtk"
|
||||||
|
```
|
||||||
|
|
||||||
|
Neat! Now I can see that [my domain is correctly identifying itself with Google
|
||||||
|
Search Console](internet/verify-site-ownership-with-dns-record.md).
|
||||||
|
|
||||||
|
[source](https://serverfault.com/a/148724)
|
||||||
49
unix/type-fewer-paths-with-brace-expansion.md
Normal file
49
unix/type-fewer-paths-with-brace-expansion.md
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
# Type Fewer Paths With Brace Expansion
|
||||||
|
|
||||||
|
Bash has a feature called _brace expansion_ that allows us to do a kind of
|
||||||
|
shorthand when writing out file paths. We can specify multiple variants
|
||||||
|
comma-separated between curly braces and they'll each be expanded into separate
|
||||||
|
arguments.
|
||||||
|
|
||||||
|
It's easier to understand this by seeing it. If we type the following (don't
|
||||||
|
hit `Enter` yet):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mkdir src/{one,two,three}
|
||||||
|
```
|
||||||
|
|
||||||
|
And then hit _Tab_:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mkdir src/one src/two src/three
|
||||||
|
```
|
||||||
|
|
||||||
|
Bash uses the portion in braces to expand into separate arguments. The part
|
||||||
|
outside the braces gets reused for each. That's where we get some savings from
|
||||||
|
typing out the same path each time.
|
||||||
|
|
||||||
|
Here is another example where we use `mv` to rename a file deeply nested in our
|
||||||
|
project:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mv projects/project1/src/app/utils/{names,constants}.js
|
||||||
|
```
|
||||||
|
|
||||||
|
We don't even have to _Tab_ it out. We can hit _Enter_ directly and `mv` gets
|
||||||
|
both arguments.
|
||||||
|
|
||||||
|
Similarly, how about we change the extension of our renamed file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mv projects/project1/src/app/utils/constants.{js,ts}
|
||||||
|
```
|
||||||
|
|
||||||
|
I've always found this feature most useful with paths and filenames, but you
|
||||||
|
can do brace expansion with any arguments.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ echo 1{3,1,6,4,9,2,7,5}
|
||||||
|
13 11 16 14 19 12 17 15
|
||||||
|
```
|
||||||
|
|
||||||
|
[source](https://www.gnu.org/software/bash/manual/html_node/Brace-Expansion.html)
|
||||||
27
vscode/synchronize-vim-clipboard-with-system-clipboard.md
Normal file
27
vscode/synchronize-vim-clipboard-with-system-clipboard.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# Synchronize Vim Clipboard With System Clipboard
|
||||||
|
|
||||||
|
When I use Vim-mode in VSCode, I _yank_ text onto the Vim clipboard by visually
|
||||||
|
selecting some text and hitting `y`. Then I can move the cursor somewhere else
|
||||||
|
in the file (or another file in VSCode) and _paste_ it by hitting `p`.
|
||||||
|
|
||||||
|
But what if I want the thing I yanked from a file to be pasted into another
|
||||||
|
program, like Chrome? Or if I've copied some text from another program and I
|
||||||
|
want to paste it into a file in VSCode?
|
||||||
|
|
||||||
|
This cross-program copy and pasting is what the _system clipboard_ on your
|
||||||
|
operating system is for. By default, the Vim clipboard is separate from the
|
||||||
|
system clipboard. I personally prefer for them to be one and the same.
|
||||||
|
|
||||||
|
To achieve this, I added the following line to my VSCode config in
|
||||||
|
`settings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"vim.useSystemClipboard": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
It takes a little getting used to having them integrated, but I've done it for
|
||||||
|
so long that it is muscle memory. It's hard to not have them integrated now.
|
||||||
|
It's even better when I have a clipboard history tool like Raycast available
|
||||||
|
for accessing past clipboard values.
|
||||||
Reference in New Issue
Block a user