1
0
mirror of https://github.com/jbranchaud/til synced 2026-07-06 17:20:33 +00:00

Compare commits

12 Commits

12 changed files with 508 additions and 1 deletions
+15 -1
View File
@@ -10,7 +10,7 @@ working across different projects via [VisualMode](https://www.visualmode.dev/).
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter). For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
_1800 TILs and counting..._ _1810 TILs and counting..._
See some of the other learning resources I work on: See some of the other learning resources I work on:
@@ -60,6 +60,7 @@ If you've learned something here, support my efforts writing daily TILs by
* [Linux](#linux) * [Linux](#linux)
* [LLM](#llm) * [LLM](#llm)
* [Mac](#mac) * [Mac](#mac)
* [Math](#math)
* [Mise](#mise) * [Mise](#mise)
* [MongoDB](#mongodb) * [MongoDB](#mongodb)
* [MySQL](#mysql) * [MySQL](#mysql)
@@ -470,6 +471,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [List PRs Awaiting Your Review](github/list-prs-awaiting-your-review.md) - [List PRs Awaiting Your Review](github/list-prs-awaiting-your-review.md)
- [Open A PR To An Unforked Repo](github/open-a-pr-to-an-unforked-repo.md) - [Open A PR To An Unforked Repo](github/open-a-pr-to-an-unforked-repo.md)
- [Open File To Specific Line In Browser](github/open-file-to-specific-line-in-browser.md) - [Open File To Specific Line In Browser](github/open-file-to-specific-line-in-browser.md)
- [Process JSON Output From gh With jq](github/process-json-output-from-gh-with-jq.md)
- [Target Another Repo When Creating A PR](github/target-another-repo-when-creating-a-pr.md) - [Target Another Repo When Creating A PR](github/target-another-repo-when-creating-a-pr.md)
- [Tell gh What The Default Repo Is](github/tell-gh-what-the-default-repo-is.md) - [Tell gh What The Default Repo Is](github/tell-gh-what-the-default-repo-is.md)
@@ -770,6 +772,10 @@ If you've learned something here, support my efforts writing daily TILs by
- [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) - [Write System Clipboard To A File](mac/write-system-clipboard-to-a-file.md)
### Math
- [Generate Permutations Of All Valid 9-ball Racks](math/generate-permutations-of-all-valid-9-ball-racks.md)
### Mise ### Mise
- [Create Umbrella Task For All Test Tasks](mise/create-umbrella-task-for-all-test-tasks.md) - [Create Umbrella Task For All Test Tasks](mise/create-umbrella-task-for-all-test-tasks.md)
@@ -1068,8 +1074,10 @@ If you've learned something here, support my efforts writing daily TILs by
- [Create A Range Of Descending Values](python/create-a-range-of-descending-values.md) - [Create A Range Of Descending Values](python/create-a-range-of-descending-values.md)
- [Deduplicate A List Into A Tuple](python/deduplicate-a-list-into-a-tuple.md) - [Deduplicate A List Into A Tuple](python/deduplicate-a-list-into-a-tuple.md)
- [Define Sequence Of Tests With Parametrize Decorator](python/define-sequence-of-tests-with-parametrize-decorator.md) - [Define Sequence Of Tests With Parametrize Decorator](python/define-sequence-of-tests-with-parametrize-decorator.md)
- [Define Typed Class Interface With Protocol](python/define-typed-class-interface-with-protocol.md)
- [Dunder Methods](python/dunder-methods.md) - [Dunder Methods](python/dunder-methods.md)
- [Easy Key-Value Aggregates With defaultdict](python/easy-key-value-aggregates-with-defaultdict.md) - [Easy Key-Value Aggregates With defaultdict](python/easy-key-value-aggregates-with-defaultdict.md)
- [Enable Pyright Type Checking In Cursor](python/enable-pyright-type-checking-in-cursor.md)
- [Get Absolute Seconds From `timedelta` Object](python/get-absolute-seconds-from-timedelta-object.md) - [Get Absolute Seconds From `timedelta` Object](python/get-absolute-seconds-from-timedelta-object.md)
- [Get Quotient And Remainder In One Operation](python/get-quotient-and-remainder-in-one-operation.md) - [Get Quotient And Remainder In One Operation](python/get-quotient-and-remainder-in-one-operation.md)
- [Install With PIP For Specific Interpreter](python/install-with-pip-for-specific-interpreter.md) - [Install With PIP For Specific Interpreter](python/install-with-pip-for-specific-interpreter.md)
@@ -1083,12 +1091,14 @@ If you've learned something here, support my efforts writing daily TILs by
- [Override The Boolean Context Of A Class](python/override-the-boolean-context-of-a-class.md) - [Override The Boolean Context Of A Class](python/override-the-boolean-context-of-a-class.md)
- [Parse Relative Time To datetime Object](python/parse-relative-time-to-datetime-object.md) - [Parse Relative Time To datetime Object](python/parse-relative-time-to-datetime-object.md)
- [Reclassify Certain Packages As Dev Dependencies](python/reclassify-certain-packages-as-dev-dependencies.md) - [Reclassify Certain Packages As Dev Dependencies](python/reclassify-certain-packages-as-dev-dependencies.md)
- [Set Up Pyright Type Checking In GitHub](python/set-up-pyright-type-checking-in-github.md)
- [Skip Specific Pytest Test Cases](python/skip-specific-pytest-test-cases.md) - [Skip Specific Pytest Test Cases](python/skip-specific-pytest-test-cases.md)
- [Sort A List Of Dataclass Instances](python/sort-a-list-of-dataclass-instances.md) - [Sort A List Of Dataclass Instances](python/sort-a-list-of-dataclass-instances.md)
- [Sort Normalized Version Of Data](python/sort-normalized-version-of-data.md) - [Sort Normalized Version Of Data](python/sort-normalized-version-of-data.md)
- [Start The Debugger When A Test Errors](python/start-the-debugger-when-a-test-errors.md) - [Start The Debugger When A Test Errors](python/start-the-debugger-when-a-test-errors.md)
- [Store And Access Immutable Data In A Tuple](python/store-and-access-immutable-data-in-a-tuple.md) - [Store And Access Immutable Data In A Tuple](python/store-and-access-immutable-data-in-a-tuple.md)
- [Test A Function With Pytest](python/test-a-function-with-pytest.md) - [Test A Function With Pytest](python/test-a-function-with-pytest.md)
- [Turn Method Into Cached Property On Class Instance](python/turn-method-into-cached-property-on-class-instance.md)
- [Use pipx To Install End User Apps](python/use-pipx-to-install-end-user-apps.md) - [Use pipx To Install End User Apps](python/use-pipx-to-install-end-user-apps.md)
- [Use `__post_init__` For `dataclass` Validations](python/use-post-init-for-dataclass-validations.md) - [Use `__post_init__` For `dataclass` Validations](python/use-post-init-for-dataclass-validations.md)
- [Use Verbose Flag To Get More Diff](python/use-verbose-flag-to-get-more-diff.md) - [Use Verbose Flag To Get More Diff](python/use-verbose-flag-to-get-more-diff.md)
@@ -1565,6 +1575,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Update The Gemfile Bundled With Version](ruby/update-the-gemfile-bundled-with-version.md) - [Update The Gemfile Bundled With Version](ruby/update-the-gemfile-bundled-with-version.md)
- [Use A Case Statement As A Cond Statement](ruby/use-a-case-statement-as-a-cond-statement.md) - [Use A Case Statement As A Cond Statement](ruby/use-a-case-statement-as-a-cond-statement.md)
- [Use dotenv In A Non-Rails Project](ruby/use-dotenv-in-a-non-rails-project.md) - [Use dotenv In A Non-Rails Project](ruby/use-dotenv-in-a-non-rails-project.md)
- [Use Rescue As Part Of Inline Statement](ruby/use-rescue-as-part-of-inline-statement.md)
- [Use Tap For Better Test Data Setup](ruby/use-tap-for-better-test-data-setup.md) - [Use Tap For Better Test Data Setup](ruby/use-tap-for-better-test-data-setup.md)
- [Using BCrypt To Create And Check Hashed Passwords](ruby/using-bcrypt-to-create-and-check-hashed-passwords.md) - [Using BCrypt To Create And Check Hashed Passwords](ruby/using-bcrypt-to-create-and-check-hashed-passwords.md)
- [What To Do When You Don't Rescue](ruby/what-to-do-when-you-dont-rescue.md) - [What To Do When You Don't Rescue](ruby/what-to-do-when-you-dont-rescue.md)
@@ -1713,6 +1724,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Curl With Cookies](unix/curl-with-cookies.md) - [Curl With Cookies](unix/curl-with-cookies.md)
- [Curling For Headers](unix/curling-for-headers.md) - [Curling For Headers](unix/curling-for-headers.md)
- [Curling With Basic Auth Credentials](unix/curling-with-basic-auth-credentials.md) - [Curling With Basic Auth Credentials](unix/curling-with-basic-auth-credentials.md)
- [Deduplicate List While Preserving Original Order](unix/deduplicate-list-while-preserving-original-order.md)
- [Determine ipv4 And ipv6 Public IP Addresses](unix/determine-ipv4-and-ipv6-public-ip-addresses.md) - [Determine ipv4 And ipv6 Public IP Addresses](unix/determine-ipv4-and-ipv6-public-ip-addresses.md)
- [Diff Two Files In Unified Format](unix/diff-two-files-in-unified-format.md) - [Diff Two Files In Unified Format](unix/diff-two-files-in-unified-format.md)
- [Different Ways To Generate A v4 UUID](unix/different-ways-to-generate-a-v4-uuid.md) - [Different Ways To Generate A v4 UUID](unix/different-ways-to-generate-a-v4-uuid.md)
@@ -1806,6 +1818,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Print A Range Of Lines For A File With Bat](unix/print-a-range-of-lines-for-a-file-with-bat.md) - [Print A Range Of Lines For A File With Bat](unix/print-a-range-of-lines-for-a-file-with-bat.md)
- [Print DateTime Represented By Unix Timestamp](unix/print-datetime-represented-by-unix-timestamp.md) - [Print DateTime Represented By Unix Timestamp](unix/print-datetime-represented-by-unix-timestamp.md)
- [Print Milliseconds In Human-Readable Format](unix/print-milliseconds-in-human-readable-format.md) - [Print Milliseconds In Human-Readable Format](unix/print-milliseconds-in-human-readable-format.md)
- [Print Out File With Bat Without Formatting](unix/print-out-file-with-bat-without-formatting.md)
- [Print Out Files In Reverse](unix/print-out-files-in-reverse.md) - [Print Out Files In Reverse](unix/print-out-files-in-reverse.md)
- [Print The Current Date In Human-Readable Format](unix/print-the-current-date-in-human-readable-format.md) - [Print The Current Date In Human-Readable Format](unix/print-the-current-date-in-human-readable-format.md)
- [Produce A Lowercase V4 UUID](unix/produce-a-lowercase-v4-uuid.md) - [Produce A Lowercase V4 UUID](unix/produce-a-lowercase-v4-uuid.md)
@@ -2080,6 +2093,7 @@ If you've learned something here, support my efforts writing daily TILs by
- [Send A Message To A Discord Channel](workflow/send-a-message-to-a-discord-channel.md) - [Send A Message To A Discord Channel](workflow/send-a-message-to-a-discord-channel.md)
- [Send A PDF To Your Kindle](workflow/send-a-pdf-to-your-kindle.md) - [Send A PDF To Your Kindle](workflow/send-a-pdf-to-your-kindle.md)
- [Set Recurring Reminders In Slack](workflow/set-recurring-reminders-in-slack.md) - [Set Recurring Reminders In Slack](workflow/set-recurring-reminders-in-slack.md)
- [Show All Linear Keyboard Shortcuts](workflow/show-all-linear-keyboard-shortcuts.md)
- [Show Linting Errors In Zed](workflow/show-linting-errors-in-zed.md) - [Show Linting Errors In Zed](workflow/show-linting-errors-in-zed.md)
- [Temporarily Hide CleanShot X Capture Previews](workflow/temporarily-hide-cleanshot-x-capture-previews.md) - [Temporarily Hide CleanShot X Capture Previews](workflow/temporarily-hide-cleanshot-x-capture-previews.md)
- [Toggle Between Stories In Storybook](workflow/toggle-between-stories-in-storybook.md) - [Toggle Between Stories In Storybook](workflow/toggle-between-stories-in-storybook.md)
+30
View File
@@ -11,6 +11,36 @@ tasks:
cmds: cmds:
- task --list - task --list
browse:list:
desc: Print deduped, newest-first TIL paths
silent: true
cmds:
- |
git log --diff-filter=A --name-only --pretty=format: -- '*/*.md' \
| grep -v '^$' \
| awk '!seen[$0]++'
browse:
desc: Pick from 5 most recent TILs (fzf) and open in browser
interactive: true
silent: true
cmds:
- |
FILE=$(task browse:list | head -5 | fzf --prompt="Open TIL: " --height=40% --reverse) || true
if [ -n "$FILE" ]; then
gh browse "$FILE"
fi
browse:latest:
desc: Open the single most recent TIL in the browser
silent: true
cmds:
- |
FILE=$(task browse:list | head -1)
if [ -n "$FILE" ]; then
gh browse "$FILE"
fi
notes: notes:
desc: Interactive picker for notes tasks desc: Interactive picker for notes tasks
cmds: cmds:
@@ -0,0 +1,58 @@
# Process JSON Output From gh With jq
The `gh` (GitHub) CLI is useful for accessing data about your profile and
projects from the terminal. With the `--json` flag, we can access the data in a
structured way which is useful for scripting.
Here is an example of pulling a list of all my repositories, limiting each
entity to just the `nameWithOwner` and `description`:
```bash
gh repo list --limit 1000 --json nameWithOwner,description
[
{
"description": "My personal site -- joshbranchaud.com",
"nameWithOwner": "jbranchaud/personal-site"
},
{
"description": "Private repo for the NOTES.md of my TIL repo",
"nameWithOwner": "jbranchaud/til-notes-private"
},
...
]
```
If I'm using the `--json` flag, then I can add in the `--jq` flag to apply a
`jq` query for additional processing of the output.
Here I convert it to a series of tuples:
```bash
gh repo list --limit 1000 --json nameWithOwner,description \
--jq '.[] | [.nameWithOwner, .description]'
[
"jbranchaud/personal-site",
"My personal site -- joshbranchaud.com"
]
[
"jbranchaud/til-notes-private",
"Private repo for the NOTES.md of my TIL repo"
]
...
```
Then I can add one more pipe to that `jq` query to turn it into _tab-separated
values_ using
[`@tsv`](https://jqlang.org/manual/v1.5/#format-strings-and-escaping):
```bash
gh repo list --limit 1000 --json nameWithOwner,description \
--jq '.[] | [.nameWithOwner, .description] | @tsv'
jbranchaud/personal-site My personal site -- joshbranchaud.com
jbranchaud/til-notes-private Private repo for the NOTES.md of my TIL repo
...
```
This is useful because I can then pipe it to another program, such as an `fzf`
command like [this repo selector that opens the selected one in the
browser](https://github.com/jbranchaud/dotfiles/commit/f964ca10c6c4db3475411c2991dc2f1dfd18c818).
@@ -0,0 +1,42 @@
# Generate Permutations Of All Valid 9-ball Racks
I wanted to produce a full listing of all valid rack arrangements for the game
of [9-ball](https://en.wikipedia.org/wiki/Nine-ball). The constraints on how a
9-ball rack can be arranged are, first, that the 1 ball must be placed at the
head of the diamond and, second, that the 9 ball must be placed at the center of
the diamond. After that, all other balls (2 through 8) can be placed in any
arrangement.
Because each of those seven remaining balls can be arranged in distinct
orderings where each ball is placed once, this is a
[_permutation_](https://en.wikipedia.org/wiki/Permutation) problem.
> In elementary combinatorics, the k-permutations, or partial permutations, are
> the ordered arrangements of k distinct elements selected from a set. When k is
> equal to the size of the set, these are the permutations in the previous
> sense.
For this problem, the seven distinct elements can be arranged into `7!` (seven
factorial) unique permutations. That is, 5040 permutations.
I can use [Ruby's `Array#permutations`
method](https://docs.ruby-lang.org/en/4.0/Array.html#method-i-permutation) to
enumerate these 5040 permutations like so:
```ruby
[2,3,4,5,6,7,8].permutation.map do |perm|
[1, *perm[0..2], 9, *perm[3..7]]
end.to_a
=> [[1, 2, 3, 4, 9, 5, 6, 7, 8],
[1, 2, 3, 4, 9, 5, 6, 8, 7],
[1, 2, 3, 4, 9, 5, 7, 6, 8],
[1, 2, 3, 4, 9, 5, 7, 8, 6],
[1, 2, 3, 4, 9, 5, 8, 6, 7],
[1, 2, 3, 4, 9, 5, 8, 7, 6],
[1, 2, 3, 4, 9, 6, 5, 7, 8],
...
[1, 8, 7, 6, 9, 5, 3, 2, 4],
[1, 8, 7, 6, 9, 5, 3, 4, 2],
[1, 8, 7, 6, 9, 5, 4, 2, 3],
[1, 8, 7, 6, 9, 5, 4, 3, 2]]
```
@@ -0,0 +1,42 @@
# Define Typed Class Interface With Protocol
In [`py-vmt`](https://github.com/jbranchaud/py-vmt) I am defining different
storage access layers for the CLI to use. I want a consistent interface that the
core CLI logic can depend on regardless of whether it is a JSON file or a SQLite
database. To achieve that I can define a class of unimplemented functions that
inherits from
[`typing.Protocol`](https://typing.python.org/en/latest/spec/protocol.html).
```python
from typing import Protocol
class SessionRepository(Protocol):
def active_session(self) -> Session | None: ...
def write_active_session(self, session) -> None: ...
def append_session(self, session) -> None: ...
def all_sessions(self) -> list[Session]: ...
def clear_active_session(self) -> None: ...
```
Notice that none of these have default implementations. The `...` indicates that
class implementing this protocol will define the implementation of those
functions.
Now, my `CliContext` class, which needs some kind of `SessionRepository` to
function can indicate as much in `__init__`.
```python
class CliContext:
def __init__(self, verbose: bool, repo: SessionRepository | None = None) -> None:
self.verbose: bool = verbose
self.active_session: Session | None = None
self.repo: SessionRepository = repo or JsonRepository()
self.active_session = self.repo.active_session()
```
If `JsonRepository` doesn't define all of the methods specified in the protocol,
then a type error will occur wherever it clashes with `SessionRepository`. Now
as I implement `SqliteRepository` I have a standard interface to build against
that I know I can seamlessly swap in.
[source](https://typing.python.org/en/latest/reference/protocols.html#simple-user-defined-protocols)
@@ -0,0 +1,24 @@
# Enable Pyright Type Checking In Cursor
In most ways [Cursor](https://cursor.com/), a clone of VS Code, behaves like VS
Code and uses the same extensions as VS Code. It even offers to clone all your
existing extensions and setup from VS Code when you first install it.
However, the Pyright type checking setup that I had in VS Code stopped working
when I opened up the same Python project in Cursor. It seems that to get Pyright
to reliably work with forks of VS Code, you need to use a compatible fork like
[Based Pyright](https://docs.basedpyright.com/latest/).
Once I installed _Based Pyright_ from the extension marketplace, I was able to
enable it in `.vscode/settings.json`:
```json
{
...,
"basedpyright.analysis.typeCheckingMode": "basic"
}
```
I may have needed to restart Cursor at this point, I cannot remember exactly.
However, once this setup was in place the helpful type checking errors started
appearing as red squiggles.
@@ -0,0 +1,62 @@
# Set Up Pyright Type Checking In GitHub
As I get into more of a PR workflow with my development of
[`py-vmt`](https://github.com/jbranchaud/py-vmt), I need to set up some basic CI
checks in GitHub. For starters I want the same `pyright` type checking that I
have locally to be run in CI for consistency.
Though my editor is set up to do Pyright type checking as I work locally, I can
also manually run it with:
```bash
$ uv run pyright
```
Pyright will look for the `tool.pyright` section in my `pyproject.toml` file
which currently looks like the following:
```toml
[tool.pyright]
include = ["src", "tests"]
```
I can get this same type checking in CI for PRs by adding the following
`.github/workflows/typecheck.yml` file:
```yaml
name: pyright
on:
pull_request:
push:
branches: [main]
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
with:
enable-cache: true
- name: Set up Python
run: uv python install
- name: Install dependencies
run: uv sync --all-extras --dev
- name: Run pyright
run: uv run pyright
```
This adds a single `typecheck` job that installs `uv`, `python`, and my project
dependencies, and then runs `uv run pyright` (just like I do locally) to perform
type checking. If `pyright` discovers any type errors, the job will fail and I
can view the output of the job to see what needs fixing. Once I have dealt with
everything, the job will quietly pass with a green check mark.
Here is [the PR](https://github.com/jbranchaud/py-vmt/pull/2) where I added this
CI job.
@@ -0,0 +1,48 @@
# Turn Method Into Cached Property On Class Instance
I have a class that encapsulates a few things including a somewhat expensive
data lookup from a file on disk. When this class is instantiated, it is
short-lived and the data that gets pulled from the file on disk is considered
fresh for the life of the instance.
```python
class CliContext:
def __init__(self, verbose: bool) -> None:
# ...
self.repo = JsonRepository()
# ...
def session_log(self) -> list[Session]:
return self.repo.load_session_log()
```
Because this method gets called from a couple places during a single lifecycle,
this class would benefit from caching it via the [`@cached_property`
decorator](https://docs.python.org/3/library/functools.html#functools.cached_property).
```python
from functools import cached_property
class CliContext:
def __init__(self, verbose: bool) -> None:
# ...
self.repo = JsonRepository()
# ...
@cached_property
def session_log(self) -> list[Session]:
return self.repo.load_session_log()
```
Now `session_log` can be treated like a property instead of a method. That means
when I want to load and access the session log, I can do `self.session_log` (no
parentheses) like I would any other property. The first time I reference it, the
method will run. Then that value will be cached and all subsequent references
will use that cache.
> Transform a method of a class into a property whose value is computed once and
> then cached as a normal attribute for the life of the instance.
Of course, anytime we use caching, we can create a footgun for ourselves. We
have to be careful that our program doesn't evolve in such a way where the
caching will create a subtle bug due to stale data.
@@ -0,0 +1,63 @@
# Use Rescue As Part Of Inline Statement
In Ruby I typically think of `rescue` as block syntax that I can use to handle
exceptions.
```ruby
begin
User.update!(password:)
rescue
puts "There was an issue updating the password"
end
```
The `rescue` keyword can also be used as part of an inline statement as a way of
providing a _fallback_ value when the first part of the statement raises.
For instance, if I'm trying to access some value on an array that happens to be
`nil`, it is going to raise:
```ruby
> scores.first
(irb):7:in '<main>': undefined method 'first' for nil (NoMethodError)
```
I can instead tack on a `rescue 0` which will give it `0` as a fallback value:
```ruby
> scores.first rescue 0
=> 0
```
Of course, there are more idiomatic ways to handle this kind of situation in
Ruby. Maybe something like this:
```ruby
> Array(scores).first || 0
=> 0
```
Another way I've seen this inline rescue used is to print out the exception
caused by that line of code, using `$!` (the global variable for the most
recently raised exception).
```ruby
> scores.first rescue puts $!
undefined method 'first' for nil
=> nil
```
That is a one-liner for the following:
```ruby
begin
scores.first
rescue => e
puts e
end
```
The big caveat that goes with this is the same one that goes with any other
blanket `rescue` block. If you are indiscriminately rescuing exceptions without
being intentional about what you are rescuing and why, you could be potentially
burying exceptions that you need to know about.
@@ -0,0 +1,71 @@
# Deduplicate List While Preserving Original Order
Usually when I want to deduplicate a list coming out of some command, I'll reach
for `sort | uniq`. This is a nice Unix trick where `uniq` removes consecutive
duplicate lines which relies on `sort` first reorganizing all lines in
alphabetically sorted order, bringing all duplicate lines together.
The caveat to using `sort | uniq` (or even `sort -u`) is that it will reorder
entries alphabetically. That means you'll lose the original order, which may
have been important.
```bash
echo "red green blue red yellow green blue red green" | tr ' ' '\n' | sort -u
blue
green
red
yellow
```
Another approach is to use `awk` which can deduplicate while preserving the
order of entries as they first appear. This can be done with a pattern that
records the count of each line in an associative array.
```bash
echo "red green blue red yellow green blue red green" | tr ' ' '\n' | awk '!seen[$0]++'
red
green
blue
yellow
```
The above pattern accepts on the first occurrence of each line and rejects on
any subsequent occurrences. That is done by adding `$0` (the current line) to
`seen` (associative array that auto-initializes inline). If it doesn't exist in
`seen` yet, then `0` is returned which is negated to a truthy value with `!`.
That entry is then incremented from `0` to `1` via the `++`. As `awk` continues
to process each line, `seen` is continually added to and incremented. The
default _action_ for `awk` is to print the line. Those truthy lines are the ones
that are printed.
An example of where this might be useful is when creating a unique listing of
all authors of a git repository while maintaining the order that they become
committers. I wanted to show this with a high-contribution public repo that I
worked on, so I referenced the [`egghead-next`
repo](https://github.com/skillrecordings/egghead-next).
```bash
git log --reverse --format='%an <%ae>' | awk '!seen[$0]++'
Joel Hooks <joelhooks@gmail.com>
johnlindquist <johnlindquist@gmail.com>
John Lindquist <johnlindquist@gmail.com>
William Johnson <w.alexander.johnson@gmail.com>
depfu[bot] <23717796+depfu[bot]@users.noreply.github.com>
Evgeniy Nagalskiy <evgeniy.nagalskiy@gmail.com>
Taylor Bell <taylorbell@gmail.com>
Maggie Appleton <maggie.fm.appleton@gmail.com>
John Lindquist <johnlindquist@work.local>
Vojta Holik <vojta@egghead.io>
Daniel Miller <dealingwith@gmail.com>
jh3y <jh3y@users.noreply.github.com>
Jhey Tompkins <jh3y@users.noreply.github.com>
Josh Branchaud <jbranchaud@gmail.com>
Lauro Silva <57044804+laurosilvacom@users.noreply.github.com>
LB <barth.laurie@gmail.com>
kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
samuelhulick <samuel@samuelhulick.com>
Ian Jones <jones58ian@gmail.com>
Zac Jones <zacjones93@gmail.com>
...
```
@@ -0,0 +1,28 @@
# Print Out File With Bat Without Formatting
The [`bat`](https://github.com/sharkdp/bat) utility is my daily driver and
replacement for anything used `cat` for before. I even have `bat` aliased to
`cat` so that I never had to rewire my muscle memory for typing `cat`.
Whether or not the creator of `cat` intended it, I'd guess that most terminal
users' main use case is printing the contents of a file. `bat` does that way
better with syntax highlighting, line numbers, and some layout formatting that
puts lines around the output and a heading with the filename.
All this formatting is great when I'm taking a quick look at a file. One way it
gets in the way is when I'm trying to highlight and copy a few lines to my
clipboard. Because the terminal is rendering lines, line numbers, and other
formatting, all that fluff gets included on the clipboard.
For this scenario, I can use the `-p` flag (or `--style=plain`) to print just
the (syntax-highlighted) file contents without all the extra formatting.
```bash
bat -p app/models/users.rb
# or
bat --style=plain app/models/users.rb
```
Another way I could have approached this was to [ignore the alias of `cat` to
`bat`](ignore-the-alias-when-running-a-command.md).
@@ -0,0 +1,25 @@
# Show All Linear Keyboard Shortcuts
Linear, the project management software, puts an incredible amount of attention
to detail into the UX and UI of their app. This includes making the app a power
tool for power users with tons of keyboard shortcuts.
I'm aware of some of Linear's keyboard shortcuts, but the discoverability of
many of them is tough.
A great way to list and browse through all of them right in the app is with
`Cmd+/`.
They are organized into sections that I can scroll through. There is also a
search box at the top of this _Keyboard Shortcuts_ panel where I can narrow down
the results to those that match a term.
A few that I'm finding immediately useful are:
- `gi` to go to my _Inbox_ in the current workspace
- `gm` to go to _My Issues_ in the current workspace
- `ow` to open a picker to switch between workspaces
Note: the _Keyboard Shortcuts_ panel lists many of the letter-based shortcuts as
being capitalized. I've found that these don't work when I hold shift. For that
reason, I've listed the above shortcuts with lowercase letters.