mirror of
https://github.com/jbranchaud/til
synced 2026-01-20 07:28:02 +00:00
Compare commits
1 Commits
e16f4ec85f
...
422676bd7a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
422676bd7a |
18
README.md
18
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).
|
||||||
|
|
||||||
_1550 TILs and counting..._
|
_1534 TILs and counting..._
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -283,7 +283,6 @@ _1550 TILs and counting..._
|
|||||||
- [Add Only Tracked Files From A Directory](git/add-only-tracked-files-from-a-directory.md)
|
- [Add Only Tracked Files From A Directory](git/add-only-tracked-files-from-a-directory.md)
|
||||||
- [Amend Author Of Previous Commit](git/amend-author-of-previous-commit.md)
|
- [Amend Author Of Previous Commit](git/amend-author-of-previous-commit.md)
|
||||||
- [Auto-Squash Those Fixup Commits](git/auto-squash-those-fixup-commits.md)
|
- [Auto-Squash Those Fixup Commits](git/auto-squash-those-fixup-commits.md)
|
||||||
- [Better Diffs With Delta](git/better-diffs-with-delta.md)
|
|
||||||
- [Caching Credentials](git/caching-credentials.md)
|
- [Caching Credentials](git/caching-credentials.md)
|
||||||
- [Change The Start Point Of A Branch](git/change-the-start-point-of-a-branch.md)
|
- [Change The Start Point Of A Branch](git/change-the-start-point-of-a-branch.md)
|
||||||
- [Check How A File Is Being Ignored](git/check-how-a-file-is-being-ignored.md)
|
- [Check How A File Is Being Ignored](git/check-how-a-file-is-being-ignored.md)
|
||||||
@@ -312,14 +311,12 @@ _1550 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)
|
||||||
- [Fix Whitespace Errors Throughout Branch Commits](git/fix-whitespace-errors-throughout-branch-commits.md)
|
|
||||||
- [Get Latest Commit Timestamp For A File](git/get-latest-commit-timestamp-for-a-file.md)
|
- [Get 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)
|
||||||
- [Grep For A Pattern On Another Branch](git/grep-for-a-pattern-on-another-branch.md)
|
- [Grep For A Pattern On Another Branch](git/grep-for-a-pattern-on-another-branch.md)
|
||||||
- [Grep Over Commit Messages](git/grep-over-commit-messages.md)
|
- [Grep Over Commit Messages](git/grep-over-commit-messages.md)
|
||||||
- [Highlight Extra Whitespace In Diff Output](git/highlight-extra-whitespace-in-diff-output.md)
|
|
||||||
- [Ignore Changes To A Tracked File](git/ignore-changes-to-a-tracked-file.md)
|
- [Ignore Changes To A Tracked File](git/ignore-changes-to-a-tracked-file.md)
|
||||||
- [Ignore Files Specific To Your Workflow](git/ignore-files-specific-to-your-workflow.md)
|
- [Ignore Files Specific To Your Workflow](git/ignore-files-specific-to-your-workflow.md)
|
||||||
- [Include A Message With Your Stashed Changes](git/include-a-message-with-your-stashed-changes.md)
|
- [Include A Message With Your Stashed Changes](git/include-a-message-with-your-stashed-changes.md)
|
||||||
@@ -383,7 +380,6 @@ _1550 TILs and counting..._
|
|||||||
- [Untrack A Directory Of Files Without Deleting](git/untrack-a-directory-of-files-without-deleting.md)
|
- [Untrack A Directory Of Files Without Deleting](git/untrack-a-directory-of-files-without-deleting.md)
|
||||||
- [Untrack A File Without Deleting It](git/untrack-a-file-without-deleting-it.md)
|
- [Untrack A File Without Deleting It](git/untrack-a-file-without-deleting-it.md)
|
||||||
- [Update The URL Of A Remote](git/update-the-url-of-a-remote.md)
|
- [Update The URL Of A Remote](git/update-the-url-of-a-remote.md)
|
||||||
- [Use External Diff Tool Like Difftastic](git/use-external-diff-tool-like-difftastic.md)
|
|
||||||
- [Using Commands With A Relative Date Format](git/using-commands-with-a-relative-date-format.md)
|
- [Using Commands With A Relative Date Format](git/using-commands-with-a-relative-date-format.md)
|
||||||
- [Verbose Commit Message](git/verbose-commit-message.md)
|
- [Verbose Commit Message](git/verbose-commit-message.md)
|
||||||
- [Viewing A File On Another Branch](git/viewing-a-file-on-another-branch.md)
|
- [Viewing A File On Another Branch](git/viewing-a-file-on-another-branch.md)
|
||||||
@@ -404,24 +400,16 @@ _1550 TILs and counting..._
|
|||||||
- [Access Go Docs Offline](go/access-go-docs-offline.md)
|
- [Access Go Docs Offline](go/access-go-docs-offline.md)
|
||||||
- [Add A Method To A Struct](go/add-a-method-to-a-struct.md)
|
- [Add A Method To A Struct](go/add-a-method-to-a-struct.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)
|
||||||
- [Check If Cobra Flag Was Set](go/check-if-cobra-flag-was-set.md)
|
|
||||||
- [Combine Two Slices](go/combine-two-slices.md)
|
- [Combine Two Slices](go/combine-two-slices.md)
|
||||||
- [Create A Slice From An Array](go/create-a-slice-from-an-array.md)
|
|
||||||
- [Detect If Stdin Comes From A Redirect](go/detect-if-stdin-comes-from-a-redirect.md)
|
|
||||||
- [Deterministically Seed A Random Number Generator](go/deterministically-seed-a-random-number-generator.md)
|
|
||||||
- [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)
|
||||||
- [Format Date And Time With Time Constants](go/format-date-and-time-with-time-constants.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 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)
|
- [Parse Flags From CLI Arguments](go/parse-flags-from-cli-arguments.md)
|
||||||
- [Produce The Zero Value Of A Generic Type](go/produce-the-zero-value-of-a-generic-type.md)
|
|
||||||
- [Redirect File To Stdin During Delve Debug](go/redirect-file-to-stdin-during-delve-debug.md)
|
|
||||||
- [Replace The Current Process With An External Command](go/replace-the-current-process-with-an-external-command.md)
|
- [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)
|
||||||
- [Sort Slice In Ascending Or Descending Order](go/sort-slice-in-ascending-or-descending-order.md)
|
- [Sort Slice In Ascending Or Descending Order](go/sort-slice-in-ascending-or-descending-order.md)
|
||||||
- [Upgrading From An Older Version On Mac](go/upgrading-from-an-older-version-on-mac.md)
|
- [Upgrading From An Older Version On Mac](go/upgrading-from-an-older-version-on-mac.md)
|
||||||
- [Write A Custom Scan Function For File IO](go/write-a-custom-scan-function-for-file-io.md)
|
|
||||||
|
|
||||||
### GROQ
|
### GROQ
|
||||||
|
|
||||||
@@ -1218,7 +1206,6 @@ _1550 TILs and counting..._
|
|||||||
- [Check If A URL Resolves To 200](ruby/check-if-a-url-resolves-to-200.md)
|
- [Check If A URL Resolves To 200](ruby/check-if-a-url-resolves-to-200.md)
|
||||||
- [Check If An Object Includes A Module](ruby/check-if-an-object-includes-a-module.md)
|
- [Check If An Object Includes A Module](ruby/check-if-an-object-includes-a-module.md)
|
||||||
- [Check Return Status Of Running A Shell Command](ruby/check-return-status-of-running-a-shell-command.md)
|
- [Check Return Status Of Running A Shell Command](ruby/check-return-status-of-running-a-shell-command.md)
|
||||||
- [Clamp To An Endless Range](ruby/clamp-to-an-endless-range.md)
|
|
||||||
- [Click On Text With Capybara](ruby/click-on-text-with-capybara.md)
|
- [Click On Text With Capybara](ruby/click-on-text-with-capybara.md)
|
||||||
- [Colorful Output With MiniTest](ruby/colorful-output-with-minitest.md)
|
- [Colorful Output With MiniTest](ruby/colorful-output-with-minitest.md)
|
||||||
- [Comparing Class Hierarchy Relationships](ruby/comparing-class-hierarchy-relationships.md)
|
- [Comparing Class Hierarchy Relationships](ruby/comparing-class-hierarchy-relationships.md)
|
||||||
@@ -1367,7 +1354,6 @@ _1550 TILs and counting..._
|
|||||||
### SQLite
|
### SQLite
|
||||||
|
|
||||||
- [Display Results In Readable Column Format](sqlite/display-results-in-readable-column-format.md)
|
- [Display Results In Readable Column Format](sqlite/display-results-in-readable-column-format.md)
|
||||||
- [Explore The Database Schema](sqlite/explore-the-database-schema.md)
|
|
||||||
|
|
||||||
### Streaming
|
### Streaming
|
||||||
|
|
||||||
@@ -1534,7 +1520,6 @@ _1550 TILs and counting..._
|
|||||||
- [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)
|
||||||
- [Manually Pass Two Git Files To Delta](unix/manually-pass-two-git-files-to-delta.md)
|
|
||||||
- [Map A Domain To localhost](unix/map-a-domain-to-localhost.md)
|
- [Map A Domain To localhost](unix/map-a-domain-to-localhost.md)
|
||||||
- [Negative Look-Ahead Search With ripgrep](unix/negative-look-ahead-search-with-ripgrep.md)
|
- [Negative Look-Ahead Search With ripgrep](unix/negative-look-ahead-search-with-ripgrep.md)
|
||||||
- [Occupy A Local Port With Netcat](unix/occupy-a-local-port-with-netcat.md)
|
- [Occupy A Local Port With Netcat](unix/occupy-a-local-port-with-netcat.md)
|
||||||
@@ -1802,7 +1787,6 @@ _1550 TILs and counting..._
|
|||||||
- [See Overlaps For A Set Of Time Zones](workflow/see-overlaps-for-a-set-of-time-zones.md)
|
- [See Overlaps For A Set Of Time Zones](workflow/see-overlaps-for-a-set-of-time-zones.md)
|
||||||
- [Send A Message To A Discord Channel](workflow/send-a-message-to-a-discord-channel.md)
|
- [Send A Message To A Discord Channel](workflow/send-a-message-to-a-discord-channel.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 Linting Errors In Zed](workflow/show-linting-errors-in-zed.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)
|
||||||
- [Update asdf Plugins With Latest Package Versions](workflow/update-asdf-plugins-with-latest-package-versions.md)
|
- [Update asdf Plugins With Latest Package Versions](workflow/update-asdf-plugins-with-latest-package-versions.md)
|
||||||
- [View The PR For The Current GitHub Branch](workflow/view-the-pr-for-the-current-github-branch.md)
|
- [View The PR For The Current GitHub Branch](workflow/view-the-pr-for-the-current-github-branch.md)
|
||||||
|
|||||||
@@ -1,43 +0,0 @@
|
|||||||
# Better Diffs With Delta
|
|
||||||
|
|
||||||
A `git diff` from the command line is relatively bare bones. It shows you
|
|
||||||
removed lines and added lines that make up a changeset with the former text in
|
|
||||||
red and the later text in green. All other contextual text is in white. I've
|
|
||||||
found this to be good enough for most of the life of my git usage. I've been
|
|
||||||
missing out though.
|
|
||||||
|
|
||||||
By using [`delta`](https://github.com/dandavison/delta) as the pager and diff
|
|
||||||
filter for `git`, I get a bunch of nice visual improvements.
|
|
||||||
|
|
||||||
- Removals and additions are red and green shaded backgrounds
|
|
||||||
- Syntax highlighting for most languages
|
|
||||||
- Highlight specific part of a line that has changed
|
|
||||||
- Visual spacing and layout is clearer
|
|
||||||
|
|
||||||
To get all of this, all I had to do was install `delta`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ brew install delta
|
|
||||||
```
|
|
||||||
|
|
||||||
And then add `delta` as both the _core_ pager and `diffFilter` in my global git
|
|
||||||
config file:
|
|
||||||
|
|
||||||
```
|
|
||||||
[core]
|
|
||||||
pager = delta
|
|
||||||
[interactive]
|
|
||||||
singleKey = true # unrelated, but nice to have
|
|
||||||
diffFilter = delta --color-only
|
|
||||||
```
|
|
||||||
|
|
||||||
It's also recommended that you use `zdiff3` for your merge conflict style,
|
|
||||||
which I already had:
|
|
||||||
|
|
||||||
```
|
|
||||||
[merge]
|
|
||||||
conflictstyle = zdiff3
|
|
||||||
```
|
|
||||||
|
|
||||||
Once you have ths all configred, try a `git diff` or `git add --patch` and see
|
|
||||||
how much more visual info you get.
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Fix Whitespace Errors Throughout Branch Commits
|
|
||||||
|
|
||||||
Let's say we've been working on some changes to our repository on a branch.
|
|
||||||
We've made several commits. We are close to putting up a PR, but we want to
|
|
||||||
make sure everything is tidied up.
|
|
||||||
|
|
||||||
We run a check and see that there are some whitespace errors that should be
|
|
||||||
fixed.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git diff main --check
|
|
||||||
README.md:1: trailing whitespace.
|
|
||||||
+# git-playground
|
|
||||||
script.sh:9: trailing whitespace.
|
|
||||||
+
|
|
||||||
```
|
|
||||||
|
|
||||||
This post isn't able to show the highlighted whitespace errors, but we can see
|
|
||||||
the warnings above.
|
|
||||||
|
|
||||||
Rather than cluttering things with an additional commit that fixes these errors
|
|
||||||
or manually cleaning up each commit, we can ask `git` to fix it for us.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git rebase --whitespace=fix main
|
|
||||||
```
|
|
||||||
|
|
||||||
That will do a manual rebase of each commit addressing the whitespace errors.
|
|
||||||
|
|
||||||
We can run the error check again and see no output, which means we are good to
|
|
||||||
go.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git diff main --check
|
|
||||||
```
|
|
||||||
|
|
||||||
See the section on `--whitespace` in `man git-apply` for more details.
|
|
||||||
|
|
||||||
[source](https://git-scm.com/book/en/v2/Customizing-Git-Git-Configuration)
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
# Highlight Extra Whitespace In Diff Output
|
|
||||||
|
|
||||||
When running a `git diff` (or `git add --patch`) I'll sometimes come across
|
|
||||||
lines that don't have any visible changes. This is usually because some
|
|
||||||
whitespace characters were either added (on accident) or removed (often by a
|
|
||||||
autoformatter).
|
|
||||||
|
|
||||||
Depending on the `core.whitespace` config, you'll probably see at least some of
|
|
||||||
the whitespace errors that git provides. By default, git only highlights
|
|
||||||
whitespace errors on added (`new`) lines. However if some extra whitespace was
|
|
||||||
originally committed and is now being removed, it won't be highlighted on the
|
|
||||||
`old` line in the diff.
|
|
||||||
|
|
||||||
We can have git always highlight whitespace errors by setting
|
|
||||||
`wsErrorHighlight` to `all` in the global git config.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git config --global diff.wsErrorHighlight all
|
|
||||||
```
|
|
||||||
|
|
||||||
Which updates the global gitconfig file with the following line:
|
|
||||||
|
|
||||||
```
|
|
||||||
[diff]
|
|
||||||
wsErrorHighlight = all
|
|
||||||
```
|
|
||||||
|
|
||||||
The `all` option is a shorthand for `old,new,context`.
|
|
||||||
|
|
||||||
See `man git-diff` for more details.
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
# Use External Diff Tool Like Difftastic
|
|
||||||
|
|
||||||
Assuming we already have a tool like `difft`
|
|
||||||
([difftastic](https://difftastic.wilfred.me.uk/introduction.html)) available on
|
|
||||||
our machine, we can use it as a diff viewer for the various `git` commands that
|
|
||||||
display a diff.
|
|
||||||
|
|
||||||
This requires a manual override which involve two pieces — an inline
|
|
||||||
configuration of `diff.external` specifying the binary of the external differ
|
|
||||||
and the `--ext-diff` flag which tells these commands to use the external diff
|
|
||||||
binary.
|
|
||||||
|
|
||||||
Here is what `git show` looks like with `difft`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git -c diff.external=difft show --ext-diff
|
|
||||||
```
|
|
||||||
|
|
||||||
Without the `--ext-diff` flag, it will fallback to the default differ despite
|
|
||||||
`diff.external` being set.
|
|
||||||
|
|
||||||
See `man git-diff` and friends for the `--ext-diff` flag. See `man git-config`
|
|
||||||
for `diff.external`.
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
# Check If Cobra Flag Was Set
|
|
||||||
|
|
||||||
When using [Cobra](https://github.com/spf13/cobra) to define a CLI, we can
|
|
||||||
specify a flag for a command like so:
|
|
||||||
|
|
||||||
```go
|
|
||||||
var Seed int64
|
|
||||||
myCmd.PersistentFlags().Int64VarP(&Seed, "seed", "", -1, "set a seed")
|
|
||||||
```
|
|
||||||
|
|
||||||
This `--seed` flag has a _default_ of `-1`. If the flag isn't specified, then
|
|
||||||
when we access that flag's value, we'll get `-1`.
|
|
||||||
|
|
||||||
But how do we differentiate between the _default_ `-1` and someone passing `-1`
|
|
||||||
to the `--seed` flag when running the program?
|
|
||||||
|
|
||||||
In the command definition, we can look at the flags and see, by name, if
|
|
||||||
specific ones were changed by user input rather than being the defaults.
|
|
||||||
|
|
||||||
```go
|
|
||||||
myCommand := &cobra.Command{
|
|
||||||
// coommand setup ...
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
if cmd.Flags().Changed("seed") {
|
|
||||||
seed, err := cmd.Flags().GetInt64("seed")
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("Seed flag is missing from `cmdFlags()`")
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf("Seed was set to %d\n", seed)
|
|
||||||
} else {
|
|
||||||
fmt.Println("Seed was not set")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If we don't want to rely on the default and instead want to specify some other
|
|
||||||
behavior when the flag is not manually set by the user, we can detect that
|
|
||||||
scenario like this.
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
# Create A Slice From An Array
|
|
||||||
|
|
||||||
Slices in Go are a flexible abstraction over arrays. We can create a slice from
|
|
||||||
an array with the `[n:m]` _slicing_ syntax. We specify the left and right
|
|
||||||
(exclusive) bounds of the array that we want to create the slice relative to.
|
|
||||||
|
|
||||||
We can exclude the lower bound which translates to the `0` index of the array.
|
|
||||||
We can exclude the left bound which translates to the end of the array. We can
|
|
||||||
even exclude both ends of the _slicing_ syntax which means creating a slice of
|
|
||||||
the entire array.
|
|
||||||
|
|
||||||
Here is an example of each of those:
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
arr := [...]string{
|
|
||||||
"taco",
|
|
||||||
"burrito",
|
|
||||||
"torta",
|
|
||||||
"enchilada",
|
|
||||||
"quesadilla",
|
|
||||||
"pozole",
|
|
||||||
}
|
|
||||||
|
|
||||||
firstTwo := arr[:2]
|
|
||||||
lastTwo := arr[len(arr)-2:]
|
|
||||||
all := arr[:]
|
|
||||||
|
|
||||||
fmt.Println("First two:", firstTwo)
|
|
||||||
// First two: [taco burrito]
|
|
||||||
|
|
||||||
fmt.Println("Last two:", lastTwo)
|
|
||||||
// Last two: [quesadilla pozole]
|
|
||||||
|
|
||||||
fmt.Println("All:", all)
|
|
||||||
// All: [taco burrito torta enchilada quesadilla pozole
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
[source](https://go.dev/blog/slices-intro#slices)
|
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
# Detect If Stdin Comes From A Redirect
|
|
||||||
|
|
||||||
Reading lines of input from `stdin` is flexible. And we may need our program to
|
|
||||||
behave differently depending on where that input is coming from. For instance,
|
|
||||||
if data is redirected or piped to our program, we scan and process it directly.
|
|
||||||
Otherwise, we need to prompt the user to enter in specific info and go from
|
|
||||||
there.
|
|
||||||
|
|
||||||
We can detect whether [`os.Stdin`](https://pkg.go.dev/os#pkg-variables) is
|
|
||||||
being piped to, redirected to, or whether we should prompt the user by looking
|
|
||||||
at the file mode descriptor of
|
|
||||||
[`os.Stdin.Stat()`](https://pkg.go.dev/os#File.Stat).
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
file, err := os.Stdin.Stat()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Error checking stdin: %v\n", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
|
|
||||||
fromTerminal := (file.Mode() & os.ModeCharDevice) != 0
|
|
||||||
fromAPipe := (file.Mode() & os.ModeNamedPipe) != 0
|
|
||||||
|
|
||||||
if fromTerminal {
|
|
||||||
fmt.Println("This is Char Device mode, let's prompt user for input")
|
|
||||||
termScanner := bufio.NewScanner(os.Stdin)
|
|
||||||
for termScanner.Scan() {
|
|
||||||
fmt.Printf("- %s\n", termScanner.Text())
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} else if fromAPipe {
|
|
||||||
fmt.Println("This is Named Pipe mode, contents piped in")
|
|
||||||
pipeScanner := bufio.NewScanner(os.Stdin)
|
|
||||||
for pipeScanner.Scan() {
|
|
||||||
fmt.Printf("- %s\n", pipeScanner.Text())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
fmt.Println("This means the input was redirected")
|
|
||||||
redirectScanner := bufio.NewScanner(os.Stdin)
|
|
||||||
for redirectScanner.Scan() {
|
|
||||||
fmt.Printf("- %s\n", redirectScanner.Text())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If `os.ModeCharDevice` then we are connected to a character device, like the
|
|
||||||
terminal. We can see if input is being piped in by checking against
|
|
||||||
`os.ModeNamedPipe`. Otherwise, there are a variety of file modes and I'm
|
|
||||||
willing to assume we're dealing with a regular file redirect at that point.
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# Deterministically Seed A Random Number Generator
|
|
||||||
|
|
||||||
If you need a random number in Go, you can always reach for the various
|
|
||||||
functions in the `rand` package.
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
for range 5 {
|
|
||||||
roll := rand.Intn(6) + 1
|
|
||||||
fmt.Printf("- %d\n", roll)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Each time I run that, I get a random set of values. Often in programming, we
|
|
||||||
want some control over the randomness. We want to _seed_ the randomness so that
|
|
||||||
it is deterministic. We want random, but the kind of random where we know how
|
|
||||||
we got there.
|
|
||||||
|
|
||||||
```go
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
seed := int64(123)
|
|
||||||
src := rand.NewSource(seed)
|
|
||||||
rng := rand.New(src)
|
|
||||||
|
|
||||||
for range 5 {
|
|
||||||
roll := rng.Intn(6) + 1
|
|
||||||
fmt.Printf("- %d\n", roll)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
In this second snippet, we create a `Source` with a specific seed value that we
|
|
||||||
can use with a custom `Rand` struct. We can then deterministically get random
|
|
||||||
numbers from it.
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# Format Date And Time With Time Constants
|
|
||||||
|
|
||||||
The Go [`time` package](https://pkg.go.dev/time) has a [`Format`
|
|
||||||
function](https://pkg.go.dev/time#Time.Format) for displaying the parts of a
|
|
||||||
date and time in standard and custom ways. It works a bit different than you
|
|
||||||
might be used to from other languages. Rather than using `strftime` identifiers
|
|
||||||
like in this string `"%B %d, %Y"`, there is a canonical date that is used as a
|
|
||||||
reference point.
|
|
||||||
|
|
||||||
That canonical date is from Janary 2nd, 2006. That was a Monday. It was at 5
|
|
||||||
seconds after 3:04PM. The Unix format of it looks like `"Mon Jan _2 15:04:05
|
|
||||||
MST 2006"`.
|
|
||||||
|
|
||||||
```
|
|
||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// This specific time pulled from `time.Format` docs
|
|
||||||
t, _ := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 2015")
|
|
||||||
|
|
||||||
// Reference date and time:
|
|
||||||
// "Mon Jan _2 15:04:05 MST 2006"
|
|
||||||
|
|
||||||
strf1 := t.Format("|2006|02|01|03:04:05|Day: Mon|")
|
|
||||||
fmt.Println("strf1:", strf1)
|
|
||||||
// strf1: |2015|25|02|11:06:39|Day: Wed|
|
|
||||||
|
|
||||||
strf2 := t.Format(time.DateTime)
|
|
||||||
strf3 := t.Format(time.RubyDate)
|
|
||||||
strf4 := t.Format(time.Kitchen)
|
|
||||||
|
|
||||||
fmt.Println("DateTime:", strf2) // DateTime: 2015-02-25 11:06:39
|
|
||||||
fmt.Println("RubyDate:", strf3) // RubyDate: Wed Feb 25 11:06:39 +0000 2015
|
|
||||||
fmt.Println("Kitchen:", strf4) // Kitchen: 11:06AM
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Though there are a [variety of useful formatting
|
|
||||||
constants](https://pkg.go.dev/time#pkg-constants) already available like
|
|
||||||
`DateTime`, `RubyDate`, `Kitchen`, etc., we can also define our own formatting
|
|
||||||
string by using the reference values for each part of a date and time.
|
|
||||||
|
|
||||||
If you want to reference the year, whether as `YYYY` or `YY`, it is always
|
|
||||||
going to be a form of `2006`, so `2006` or `06` respectively. Even though the
|
|
||||||
above time variable is in February, our format strings will always need to use
|
|
||||||
one of `Jan`, `January`, `01` or `1`.
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Produce The Zero Value For A Generic Type
|
|
||||||
|
|
||||||
While writing a _pop_ function that would work with slices of a generic type, I
|
|
||||||
ran into the issue of needing to produce a zero value of type `T` when
|
|
||||||
returning early for an empty slice.
|
|
||||||
|
|
||||||
The way to arbitrarily get the zero value of a generic in Go is with `*new(T)`.
|
|
||||||
|
|
||||||
I was able to use this in my `Pop` function like so:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func Pop[T any](slice []T) (T, error) {
|
|
||||||
if len(slice) == 0 {
|
|
||||||
return *new(T), fmt.Errorf("cannot pop an empty slice")
|
|
||||||
}
|
|
||||||
|
|
||||||
lastItem := slice[len(slice)-1]
|
|
||||||
|
|
||||||
slice = slice[:len(slice)-1]
|
|
||||||
|
|
||||||
return lastItem, nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
If this is happening in multiple functions and we want a more self-documenting
|
|
||||||
approach, we can pull it out into a function `zero`:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func zero[T any]() T {
|
|
||||||
return *new(T)
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
# Redirect File To Stdin During Delve Debug
|
|
||||||
|
|
||||||
I have a go program that accepts input from stdin. The way I've been running
|
|
||||||
the program as I develop it is to redirect the output of some sample files to
|
|
||||||
the program.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ go run . < sample/001.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
When I then go to debug this program with
|
|
||||||
[Delve](https://github.com/go-delve/delve), I'd still like to be able to
|
|
||||||
redirect a file into the program to reproduce the exact behavior I'm seeing.
|
|
||||||
|
|
||||||
The following won't work:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ dlv debug . < samples/001.txt
|
|
||||||
Stdin is not a terminal, use '-r' to specify redirects for the target process or --allow-non-terminal-interactive=true if you really want to specify a redirect for Delve
|
|
||||||
```
|
|
||||||
|
|
||||||
Fortunately, `dlv` sees what I'm trying to do and makes a recommendation. The
|
|
||||||
`-r` flag can be used to specify redirects for the target process. The [`dlv`
|
|
||||||
redirect
|
|
||||||
docs](https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_redirect.md)
|
|
||||||
explain that `-r` can be passed a `source:destination`. The `source` is `stdin`
|
|
||||||
by default, but can also be `stdout` and `stderr`.
|
|
||||||
|
|
||||||
I can redirect my file into the debugging session of my program like so:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ dlv debug . -r stdin:samples/001.txt
|
|
||||||
```
|
|
||||||
|
|
||||||
Or even more succinctly:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ dlv debug . -r samples/001.txt
|
|
||||||
```
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
# Write A Custom Scan Function For File IO
|
|
||||||
|
|
||||||
By default a [`bufio.Scanner`](https://pkg.go.dev/bufio#Scanner) will scan
|
|
||||||
input line-by-line. In other words, splitting on newlines such that each
|
|
||||||
iteration will emit everything up to the next newline character.
|
|
||||||
|
|
||||||
We can write our own `SplitFunc` and override the default one by calling
|
|
||||||
`scanner.Split` with it. Our custom scan function needs to match the type
|
|
||||||
signature of [`SplitFunc`](https://pkg.go.dev/bufio#SplitFunc).
|
|
||||||
|
|
||||||
Here is a custom one that emits each individual character but omits the
|
|
||||||
newlines.
|
|
||||||
|
|
||||||
```go
|
|
||||||
func ScanChar(data []byte, atEOF bool) (int, []byte, error) {
|
|
||||||
if atEOF || len(data) == 0 {
|
|
||||||
return 0, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
start := 0
|
|
||||||
for start < len(data) {
|
|
||||||
if !utf8.FullRune(data[start:]) {
|
|
||||||
return 0, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
r, size := utf8.DecodeRune(data[start:])
|
|
||||||
if r == utf8.RuneError {
|
|
||||||
return 0, nil, fmt.Errorf("invalid UTF-8 encoding")
|
|
||||||
}
|
|
||||||
|
|
||||||
if r != '\n' {
|
|
||||||
return start + size, data[start:start+size], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// found a \n, advance the start position
|
|
||||||
start += size
|
|
||||||
}
|
|
||||||
|
|
||||||
return start, nil, nil
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
We can then use thi `ScanChar` function with a `bufio.Scanner` like so:
|
|
||||||
|
|
||||||
```go
|
|
||||||
func ReadFileByCharacter(file io.Reader) {
|
|
||||||
scanner := bufio.NewScanner(file)
|
|
||||||
|
|
||||||
// override default SplitFunc
|
|
||||||
scanner.Split(scanChar)
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
char := scanner.Text()
|
|
||||||
|
|
||||||
fmt.Printf("- %s\n", char)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Clamp To An Endless Range
|
|
||||||
|
|
||||||
The
|
|
||||||
[`Comparable#clamp`](https://ruby-doc.org/3.3.6/Comparable.html#method-i-clamp)
|
|
||||||
method allows us to specify the bounds of a value we want. If the target value
|
|
||||||
is between the bounds, then we get that value. Otherwise, we gets the nearest
|
|
||||||
end of the bounds.
|
|
||||||
|
|
||||||
We can even pass a range to `#clamp` instead of separate lower and upper bound
|
|
||||||
values. Because Ruby has beginless and endless ranges, this gives us the
|
|
||||||
ergonomics to, say, clamp to any non-negative value with a `0..` endless range.
|
|
||||||
|
|
||||||
Here is what that looks like:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
> 22.clamp(0..)
|
|
||||||
=> 22
|
|
||||||
> (-33).clamp(0..)
|
|
||||||
=> 0
|
|
||||||
> 0.clamp(0..)
|
|
||||||
=> 0
|
|
||||||
```
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
# Explore The Database Schema
|
|
||||||
|
|
||||||
The first thing I like to do when connecting to a database is get a quick lay
|
|
||||||
of the land. What are the tables and what do they look like?
|
|
||||||
|
|
||||||
I can list all tables with the `.tables` dot-command.
|
|
||||||
|
|
||||||
```sql
|
|
||||||
sqlite> .tables
|
|
||||||
ingredient_amounts ingredients recipes
|
|
||||||
```
|
|
||||||
|
|
||||||
I can then look at the `create table` statement for specific tables to see what
|
|
||||||
their schema looks like:
|
|
||||||
|
|
||||||
```sql
|
|
||||||
sqlite> .schema recipes
|
|
||||||
CREATE TABLE recipes (
|
|
||||||
id integer primary key,
|
|
||||||
name varchar not null,
|
|
||||||
description text not null,
|
|
||||||
instructions text not null
|
|
||||||
);
|
|
||||||
```
|
|
||||||
|
|
||||||
The `.schema` dot-command can also be used without any argument and it will
|
|
||||||
display the schema for all tables of all connected databases.
|
|
||||||
|
|
||||||
Run `.help` from the `sqlite3` prompt for more dot-command options.
|
|
||||||
|
|
||||||
[source](https://www.sqlite.org/cli.html#querying_the_database_schema)
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# Manually Pass Two Git Files To Delta
|
|
||||||
|
|
||||||
I recently [wired up `delta` as my default pager and differ for
|
|
||||||
`git`](git/better-diffs-with-delta.md). However, when I installed `delta`, I
|
|
||||||
first wanted to see what its diff output looked like.
|
|
||||||
|
|
||||||
How can I pass two versions of the same file from `git` to `delta`?
|
|
||||||
|
|
||||||
I can show the current contents of a file with `git show` referencing the
|
|
||||||
`HEAD` commit.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git show HEAD:main.go
|
|
||||||
```
|
|
||||||
|
|
||||||
Similiarly, I can show the contents of that file _one_ commit ago with `HEAD~`.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ git show HEAD~:main.go
|
|
||||||
```
|
|
||||||
|
|
||||||
I can then pass each of those commands as virtual files to `delta` using the
|
|
||||||
`<()` syntax. The older file goes first and the newer second.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
$ delta <(git show HEAD~:main.go) <(git show HEAD:main.go)
|
|
||||||
```
|
|
||||||
|
|
||||||
That works and comes in handy if you need to compare two things that aren't
|
|
||||||
necessarily files or aren't necessarily under version control. However, in
|
|
||||||
hindsight, I'd say it is easier to add delta as the pager and differ and try it
|
|
||||||
out directly.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
# Show Linting Errors In Zed
|
|
||||||
|
|
||||||
When working in a language like TypeScript or Go, the language server tooling
|
|
||||||
in [Zed](https://zed.dev/) can draw my attention to errors in my code. This
|
|
||||||
could be an unrecognized function or variable, a type error, or a syntax error.
|
|
||||||
When these linting errors are detected, the editor underlines them with a red
|
|
||||||
squiggly. I can hover over offending token or statement and see what the error
|
|
||||||
is.
|
|
||||||
|
|
||||||
There are also a few mouse-free ways to do this.
|
|
||||||
|
|
||||||
First, I can hit `F8` to jump to the next one of these errors in the current
|
|
||||||
file. That will move my cursor to that location and display a small overlay
|
|
||||||
with the error details.
|
|
||||||
|
|
||||||
Second, assuming Vim mode, I can navigate my cursor over a specific highlighted
|
|
||||||
token and then hit `Shift+k`. That will pop open the same small overlay to
|
|
||||||
display the error details.
|
|
||||||
|
|
||||||
Third, I can hit `Cmd+Shift+M` to open the _Project Diagnostics_ tab which
|
|
||||||
displays a series of file buffer results with the offending lines and the error
|
|
||||||
description.
|
|
||||||
Reference in New Issue
Block a user