mirror of
https://github.com/jbranchaud/til
synced 2026-01-18 14:38:01 +00:00
Compare commits
8 Commits
e4dcfd76e8
...
7657138d75
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7657138d75 | ||
|
|
b329d36888 | ||
|
|
e0db60f6ce | ||
|
|
5c81ddc151 | ||
|
|
6af86bd407 | ||
|
|
98d8249cf1 | ||
|
|
7f1c243310 | ||
|
|
bc767a0ad3 |
12
README.md
12
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..._
|
_1520 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)
|
||||||
@@ -568,6 +570,10 @@ _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)
|
||||||
|
|
||||||
### 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)
|
||||||
@@ -1182,6 +1188,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 +1232,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 +1300,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)
|
||||||
@@ -1537,6 +1546,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)
|
||||||
|
|||||||
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.
|
||||||
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)
|
||||||
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)
|
||||||
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.
|
||||||
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)
|
||||||
@@ -6,7 +6,7 @@ convert it using the `ebook-convert` binary from `Calibre`.
|
|||||||
First, install `Calibre`:
|
First, install `Calibre`:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ brew cask install calibre
|
$ brew install --cask calibre
|
||||||
```
|
```
|
||||||
|
|
||||||
Then convert your ePub using `ebook-convert`:
|
Then convert your ePub using `ebook-convert`:
|
||||||
|
|||||||
Reference in New Issue
Block a user