mirror of
https://github.com/jbranchaud/til
synced 2026-01-15 21:18: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).
|
||||
|
||||
_1514 TILs and counting..._
|
||||
_1531 TILs and counting..._
|
||||
|
||||
---
|
||||
|
||||
@@ -41,6 +41,7 @@ _1514 TILs and counting..._
|
||||
* [Internet](#internet)
|
||||
* [Java](#java)
|
||||
* [JavaScript](#javascript)
|
||||
* [jj](#jj)
|
||||
* [jq](#jq)
|
||||
* [Kitty](#kitty)
|
||||
* [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 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)
|
||||
- [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)
|
||||
@@ -397,9 +399,12 @@ _1514 TILs and counting..._
|
||||
|
||||
- [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)
|
||||
- [Combine Two Slices](go/combine-two-slices.md)
|
||||
- [Do Something N Times](go/do-something-n-times.md)
|
||||
- [Find Executables Installed By Go](go/find-executables-installed-by-go.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)
|
||||
- [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)
|
||||
@@ -455,6 +460,7 @@ _1514 TILs and counting..._
|
||||
- [Get Random Images From Unsplash](internet/get-random-images-from-unsplash.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)
|
||||
- [Verify Site Ownership With DNS Record](internet/verify-site-ownership-with-dns-record.md)
|
||||
|
||||
### Java
|
||||
|
||||
@@ -568,6 +574,11 @@ _1514 TILs and counting..._
|
||||
- [Yarn Commands Without The Emojis](javascript/yarn-commands-without-the-emojis.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
|
||||
|
||||
- [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)
|
||||
- [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)
|
||||
|
||||
### 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)
|
||||
- [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)
|
||||
- [Organize Pages In Route Groups](nextjs/organize-pages-in-route-groups.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)
|
||||
- [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)
|
||||
- [Export Query Results To A CSV](postgres/export-query-results-to-a-csv.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 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)
|
||||
@@ -815,6 +829,7 @@ _1514 TILs and counting..._
|
||||
- [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)
|
||||
- [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)
|
||||
- [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)
|
||||
@@ -1182,6 +1197,7 @@ _1514 TILs and counting..._
|
||||
- [Audit Your Ruby Project For Any CVEs](ruby/audit-your-ruby-project-for-any-cves.md)
|
||||
- [Assoc For Hashes](ruby/assoc-for-hashes.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)
|
||||
- [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)
|
||||
@@ -1225,6 +1241,7 @@ _1514 TILs and counting..._
|
||||
- [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)
|
||||
- [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 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)
|
||||
@@ -1292,6 +1309,7 @@ _1514 TILs and counting..._
|
||||
- [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)
|
||||
- [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)
|
||||
- [Summing Collections](ruby/summing-collections.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 The Available JDKs](unix/list-the-available-jdks.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)
|
||||
- [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)
|
||||
@@ -1537,6 +1556,7 @@ _1514 TILs and counting..._
|
||||
- [Switch Versions of a Brew Formula](unix/switch-versions-of-a-brew-formula.md)
|
||||
- [Tell direnv To Load The Env File](unix/tell-direnv-to-load-the-env-file.md)
|
||||
- [Touch Access And Modify Times Individually](unix/touch-access-and-modify-times-individually.md)
|
||||
- [Type Fewer Paths With Brace Expansion](unix/type-fewer-paths-with-brace-expansion.md)
|
||||
- [Undo Changes Made To Current Terminal Prompt](unix/undo-changes-made-to-current-terminal-prompt.md)
|
||||
- [Undo Some Command Line Editing](unix/undo-some-command-line-editing.md)
|
||||
- [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)
|
||||
- [Pop Open The Quick Fix Window](vscode/pop-open-the-quick-fix-window.md)
|
||||
- [Step Through Project-Wide Search Results](vscode/step-through-project-wide-search-results.md)
|
||||
- [Synchronize Vim Clipboard With System Clipboard](vscode/synchronize-vim-clipboard-with-system-clipboard.md)
|
||||
- [Toggle Between Terminals](vscode/toggle-between-terminals.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
|
||||
list = [3,7,4,15,9,1,2]
|
||||
|
||||
list.minmax
|
||||
#=> [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