mirror of
https://github.com/jbranchaud/til
synced 2026-01-17 05:58:01 +00:00
Compare commits
28 Commits
6ebc365267
...
b6780acfe0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b6780acfe0 | ||
|
|
f9c0a566eb | ||
|
|
527038ca23 | ||
|
|
b972673008 | ||
|
|
cc31aae25a | ||
|
|
26f30c3225 | ||
|
|
e14da2f207 | ||
|
|
b7d4a62ecb | ||
|
|
1ad41b9776 | ||
|
|
11716a8fb5 | ||
|
|
5e19d53382 | ||
|
|
c8aa6ee506 | ||
|
|
9c0c9222f9 | ||
|
|
855251e478 | ||
|
|
4e5ba0ce4c | ||
|
|
63a92cbc29 | ||
|
|
8438025005 | ||
|
|
a3be570a32 | ||
|
|
464a2af6db | ||
|
|
8801f39df0 | ||
|
|
aeb55efc3c | ||
|
|
a92af09fea | ||
|
|
43e6433fd6 | ||
|
|
88e675b9a3 | ||
|
|
f5286c1f41 | ||
|
|
8787e43458 | ||
|
|
f658a31435 | ||
|
|
db00ec69c2 |
33
README.md
33
README.md
@@ -10,7 +10,11 @@ pairing with smart people at Hashrocket.
|
||||
|
||||
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
|
||||
|
||||
_1534 TILs and counting..._
|
||||
_1556 TILs and counting..._
|
||||
|
||||
See some of the other learning resources I work on:
|
||||
- [Ruby Operator Lookup](https://www.visualmode.dev/ruby-operators)
|
||||
- [Vim Un-Alphabet](https://www.youtube.com/playlist?list=PL46-cKSxMYYCMpzXo6p0Cof8hJInYgohU)
|
||||
|
||||
---
|
||||
|
||||
@@ -283,6 +287,7 @@ _1534 TILs and counting..._
|
||||
- [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)
|
||||
- [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)
|
||||
- [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)
|
||||
@@ -311,12 +316,14 @@ _1534 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)
|
||||
- [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 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)
|
||||
- [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)
|
||||
- [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 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)
|
||||
@@ -380,6 +387,7 @@ _1534 TILs and counting..._
|
||||
- [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)
|
||||
- [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)
|
||||
- [Verbose Commit Message](git/verbose-commit-message.md)
|
||||
- [Viewing A File On Another Branch](git/viewing-a-file-on-another-branch.md)
|
||||
@@ -399,17 +407,28 @@ _1534 TILs and counting..._
|
||||
|
||||
- [Access Go Docs Offline](go/access-go-docs-offline.md)
|
||||
- [Add A Method To A Struct](go/add-a-method-to-a-struct.md)
|
||||
- [Basic Delve Debugging Session](go/basic-delve-debugging-session.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)
|
||||
- [Connect To A SQLite Database](go/connect-to-a-sqlite-database.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)
|
||||
- [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)
|
||||
- [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)
|
||||
- [Pass A Struct To A Function](go/pass-a-struct-to-a-function.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)
|
||||
- [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)
|
||||
- [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
|
||||
|
||||
@@ -1206,6 +1225,7 @@ _1534 TILs and counting..._
|
||||
- [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 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)
|
||||
- [Colorful Output With MiniTest](ruby/colorful-output-with-minitest.md)
|
||||
- [Comparing Class Hierarchy Relationships](ruby/comparing-class-hierarchy-relationships.md)
|
||||
@@ -1255,6 +1275,7 @@ _1534 TILs and counting..._
|
||||
- [Iterate With An Offset Index](ruby/iterate-with-an-offset-index.md)
|
||||
- [Include Extra Context In A Honeybadger Notify](ruby/include-extra-context-in-a-honeybadger-notify.md)
|
||||
- [Ins And Outs Of Pry](ruby/ins-and-outs-of-pry.md)
|
||||
- [Install Latest Version Of Ruby With asdf](ruby/install-latest-version-of-ruby-with-asdf.md)
|
||||
- [Invoking Rake Tasks Multiple Times](ruby/invoking-rake-tasks-multiple-times.md)
|
||||
- [IRB Has Built-In Benchmarking With Ruby 3](ruby/irb-has-built-in-benchmarking-with-ruby-3.md)
|
||||
- [Jump Out Of A Nested Context With Throw/Catch](ruby/jump-out-of-a-nested-context-with-throw-catch.md)
|
||||
@@ -1287,6 +1308,7 @@ _1534 TILs and counting..._
|
||||
- [Question Mark Operator](ruby/question-mark-operator.md)
|
||||
- [Rake Only Lists Tasks With Descriptions](ruby/rake-only-lists-tasks-with-descriptions.md)
|
||||
- [Read The First Line From A File](ruby/read-the-first-line-from-a-file.md)
|
||||
- [Refer To Implicit Block Argument With It](ruby/refer-to-implicit-block-argument-with-it.md)
|
||||
- [Rendering ERB](ruby/rendering-erb.md)
|
||||
- [Replace The Current Process With An External Command](ruby/replace-the-current-process-with-an-external-command.md)
|
||||
- [Require Entire Gemfile In Pry Session](ruby/require-entire-gemfile-in-pry-session.md)
|
||||
@@ -1354,6 +1376,7 @@ _1534 TILs and counting..._
|
||||
### SQLite
|
||||
|
||||
- [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
|
||||
|
||||
@@ -1520,6 +1543,7 @@ _1534 TILs and counting..._
|
||||
- [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)
|
||||
- [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)
|
||||
- [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)
|
||||
@@ -1547,6 +1571,7 @@ _1534 TILs and counting..._
|
||||
- [Search History](unix/search-history.md)
|
||||
- [Search Man Page Descriptions](unix/search-man-page-descriptions.md)
|
||||
- [Securely Remove Files](unix/securely-remove-files.md)
|
||||
- [See Where asdf Gets Current Tool Version](unix/see-where-asdf-gets-current-tool-version.md)
|
||||
- [Set The asdf Package Version For A Single Shell](unix/set-the-asdf-package-version-for-a-single-shell.md)
|
||||
- [Show A File Preview When Searching With FZF](unix/show-a-file-preview-when-searching-with-fzf.md)
|
||||
- [Show Disk Usage For The Current Directory](unix/show-disk-usage-for-the-current-directory.md)
|
||||
@@ -1770,6 +1795,7 @@ _1534 TILs and counting..._
|
||||
- [Add Subtitles To Existing Mux Video Asset](workflow/add-subtitles-to-existing-mux-video-asset.md)
|
||||
- [Access 1Password Credential From CLI](workflow/access-1password-credential-from-cli.md)
|
||||
- [Allow Key-Repeating With Cursor](workflow/allow-key-repeating-with-cursor.md)
|
||||
- [Break Justfile Into Separate Hidden Steps](workflow/break-justfile-into-separate-hidden-steps.md)
|
||||
- [Change Window Name In iTerm](workflow/change-window-name-in-iterm.md)
|
||||
- [Configure Email Redirect With Cloudflare](workflow/configure-email-redirect-with-cloudflare.md)
|
||||
- [Convert An ePub Document To PDF On Mac](workflow/convert-an-epub-document-to-pdf-on-mac.md)
|
||||
@@ -1787,6 +1813,7 @@ _1534 TILs and counting..._
|
||||
- [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)
|
||||
- [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)
|
||||
- [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)
|
||||
@@ -1838,11 +1865,11 @@ I shamelessly stole this idea from
|
||||
|
||||
* [Today I Learned by Hashrocket](https://til.hashrocket.com)
|
||||
* [jwworth/til](https://github.com/jwworth/til)
|
||||
* [thoughtbot/til](https://github.com/thoughtbot/til)
|
||||
* [til.simonwillison.net](https://til.simonwillison.net/)
|
||||
|
||||
## License
|
||||
|
||||
© 2015-2022 Josh Branchaud
|
||||
© 2015-2025 Josh Branchaud
|
||||
|
||||
This repository is licensed under the MIT license. See `LICENSE` for
|
||||
details.
|
||||
|
||||
43
git/better-diffs-with-delta.md
Normal file
43
git/better-diffs-with-delta.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# 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.
|
||||
39
git/fix-whitespace-errors-throughout-branch-commits.md
Normal file
39
git/fix-whitespace-errors-throughout-branch-commits.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 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)
|
||||
30
git/highlight-extra-whitespace-in-diff-output.md
Normal file
30
git/highlight-extra-whitespace-in-diff-output.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# 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.
|
||||
23
git/use-external-diff-tool-like-difftastic.md
Normal file
23
git/use-external-diff-tool-like-difftastic.md
Normal file
@@ -0,0 +1,23 @@
|
||||
# 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`.
|
||||
63
go/basic-delve-debugging-session.md
Normal file
63
go/basic-delve-debugging-session.md
Normal file
@@ -0,0 +1,63 @@
|
||||
# Basic Delve Debugging Session
|
||||
|
||||
When using [delve](https://github.com/go-delve/delve) to debug a Go program,
|
||||
these are the series of things I usually find myself doing.
|
||||
|
||||
First, I start running the program with `dlv` including any arguments after a `--` (in my case, the `solve` subcommand and a filename).
|
||||
|
||||
```bash
|
||||
$ dlv debug . -- solve samples/001.txt
|
||||
```
|
||||
|
||||
`dlv` starts up and is ready to run my program from the beginning. I'll need to
|
||||
set a couple breakpoints before continuing. I do this with the `break` command,
|
||||
specifying the filename and line number.
|
||||
|
||||
```
|
||||
(dlv) break main.go:528
|
||||
Breakpoint 1 set at 0x10c1a5bea for main.traversePuzzleIterative() ./main.go:528
|
||||
(dlv) break main.go:599
|
||||
Breakpoint 2 set at 0x10c1a6dcc for main.traversePuzzleIterative() ./main.go:599
|
||||
```
|
||||
|
||||
Now I can continue which will run the program until hitting a breakpoint.
|
||||
|
||||
```
|
||||
(dlv) continue
|
||||
> [Breakpoint 2] main.traversePuzzleIterative() ./main.go:599 (hits goroutine(1):1 total:1) (PC: 0x10c1a6dcc)
|
||||
594: }
|
||||
595: }
|
||||
596:
|
||||
597: topStackFrame := stack[len(stack)-1]
|
||||
598: // if the current stack frame has more values, try the next
|
||||
=> 599: if len(topStackFrame.PossibleValues) > 0 {
|
||||
600: nextValue := topStackFrame.PossibleValues[0]
|
||||
601: topStackFrame.PossibleValues = topStackFrame.PossibleValues[1:]
|
||||
602: topStackFrame.CurrValue = nextValue
|
||||
603:
|
||||
604: // Undo the last placement and make a new one
|
||||
```
|
||||
|
||||
I can see the context around the line we've stopped on. From here I can dig
|
||||
into the current state of the program by looking at local variables (`locals`)
|
||||
or printing out a specific value (`print someVar`). I can continue to step
|
||||
through the program line by line with `next` or eventually run `continue` to
|
||||
proceed to the next breakpoint.
|
||||
|
||||
```
|
||||
(dlv) locals
|
||||
diagnostics = main.Diagnostics {BacktrackCount: 0, NodeVisitCount: 1, ValidityCheckCount: 2,...+2 more}
|
||||
stack = []main.StackData len: 1, cap: 1, [...]
|
||||
emptyCellPositions = [][]int len: 3, cap: 4, [...]
|
||||
emptyCellIndex = 1
|
||||
status = "Invalid"
|
||||
topStackFrame = main.StackData {RowIndex: 1, ColumnIndex: 7, PossibleValues: []int len: 8, cap: 8, [...],...+1 more}
|
||||
(dlv) print topStackFrame
|
||||
main.StackData {
|
||||
RowIndex: 1,
|
||||
ColumnIndex: 7,
|
||||
PossibleValues: []int len: 8, cap: 8, [2,3,4,5,6,7,8,9],
|
||||
CurrValue: 1,}
|
||||
(dlv) next
|
||||
> main.traversePuzzleIterative() ./main.go:600 (PC: 0x10c1a6dea)
|
||||
```
|
||||
41
go/check-if-cobra-flag-was-set.md
Normal file
41
go/check-if-cobra-flag-was-set.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# 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.
|
||||
50
go/connect-to-a-sqlite-database.md
Normal file
50
go/connect-to-a-sqlite-database.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Connect To A SQLite Database
|
||||
|
||||
Using the `database/sql` module and the `github.com/mattn/go-sqlite3` package,
|
||||
we can connect to a SQLite database and run some queries. In my case, I have a
|
||||
SQLite connection string exported to my environment, so I can access that with
|
||||
`os.Getenv`. It's a local SQLite file, `./test.db`.
|
||||
|
||||
Calling `sql.Open`, I'm able to connect with a SQLite3 driver to the database
|
||||
at that connection string. The `setupDatabase` function returns that database
|
||||
connection pointer. Things like `Exec` and `QueryRow` can be called on `db`. I
|
||||
also need to make sure I close the connection to the database with a `defer`.
|
||||
|
||||
Here is a full example of connecting to a local SQLite database and inserting a
|
||||
record:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func setupDatabase() *sql.DB {
|
||||
databaseString := os.Getenv("GOOSE_DBSTRING")
|
||||
if len(databaseString) == 0 {
|
||||
fmt.Println("Error retrieving `GOOSE_DBSTRING` from env")
|
||||
os.Exit(1)
|
||||
}
|
||||
db, err := sql.Open("sqlite3", databaseString)
|
||||
if err != nil {
|
||||
fmt.Printf("Error opening database: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func main() {
|
||||
db := setupDatabase()
|
||||
defer db.Close()
|
||||
|
||||
sql := `insert into users (name) values (?);`
|
||||
|
||||
db.Exec(sql, "Josh")
|
||||
}
|
||||
```
|
||||
44
go/create-a-slice-from-an-array.md
Normal file
44
go/create-a-slice-from-an-array.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# 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)
|
||||
59
go/detect-if-stdin-comes-from-a-redirect.md
Normal file
59
go/detect-if-stdin-comes-from-a-redirect.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# 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.
|
||||
49
go/deterministically-seed-a-random-number-generator.md
Normal file
49
go/deterministically-seed-a-random-number-generator.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# 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.
|
||||
51
go/format-date-and-time-with-time-constants.md
Normal file
51
go/format-date-and-time-with-time-constants.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# 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`.
|
||||
65
go/pass-a-struct-to-a-function.md
Normal file
65
go/pass-a-struct-to-a-function.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Pass A Struct To A Function
|
||||
|
||||
Go operates as _pass-by-value_ which means that when we pass a struct to a
|
||||
function, the receiving function gets a copy of the struct. Two things worth
|
||||
noticing about that are 1) an extra memory allocation happens when calling the
|
||||
function and 2) altering the struct does not affect the original in the calling
|
||||
context.
|
||||
|
||||
On the other hand, we can have a function that takes a pointer to a struct.
|
||||
When we call that function, we have a reference to the memory location of the
|
||||
struct instead of a copy of the struct. That means no additional allocation and
|
||||
modifications to the dereferenced struct are modifications to the original in
|
||||
the calling context.
|
||||
|
||||
Here is an example that demonstrates both of these. Notice the printed output
|
||||
that is included in comments at the end which shows memory locations and
|
||||
contents of the struct at various points.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Order struct {
|
||||
Item string
|
||||
Quantity int
|
||||
DineIn bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
order := Order{Item: "taco", Quantity: 3, DineIn: true}
|
||||
|
||||
fmt.Println("Order:", order)
|
||||
fmt.Printf("main - Loc: %p\n", &order)
|
||||
|
||||
doubledOrder := doubleOrder(order)
|
||||
|
||||
fmt.Println("Double Order:", doubledOrder)
|
||||
fmt.Println("Original Order:", order)
|
||||
|
||||
doubleOrderPtr(&order)
|
||||
|
||||
fmt.Println("Double Order Ptr:", order)
|
||||
}
|
||||
|
||||
func doubleOrder(order Order) Order {
|
||||
fmt.Printf("doubleOrder - Loc: %p\n", &order)
|
||||
order.Quantity *= 2
|
||||
|
||||
return order
|
||||
}
|
||||
|
||||
func doubleOrderPtr(order *Order) {
|
||||
fmt.Printf("doubleOrderPtr - Loc: %p\n", order)
|
||||
(*order).Quantity *= 2
|
||||
}
|
||||
|
||||
// Order: {taco 3 true}
|
||||
// main - Loc: 0xc0000b4000
|
||||
// doubleOrder - Loc: 0xc0000b4040
|
||||
// Double Order: {taco 6 true}
|
||||
// Original Order: {taco 3 true}
|
||||
// doubleOrderPtr - Loc: 0xc0000b4000
|
||||
// Double Order Ptr: {taco 6 true}
|
||||
```
|
||||
32
go/produce-the-zero-value-of-a-generic-type.md
Normal file
32
go/produce-the-zero-value-of-a-generic-type.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 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)
|
||||
}
|
||||
```
|
||||
39
go/redirect-file-to-stdin-during-delve-debug.md
Normal file
39
go/redirect-file-to-stdin-during-delve-debug.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 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
|
||||
```
|
||||
58
go/write-a-custom-scan-function-for-file-io.md
Normal file
58
go/write-a-custom-scan-function-for-file-io.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 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)
|
||||
}
|
||||
}
|
||||
```
|
||||
22
ruby/clamp-to-an-endless-range.md
Normal file
22
ruby/clamp-to-an-endless-range.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# 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
|
||||
```
|
||||
54
ruby/install-latest-version-of-ruby-with-asdf.md
Normal file
54
ruby/install-latest-version-of-ruby-with-asdf.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Install Latest Version Of Ruby With asdf
|
||||
|
||||
When I check the `asdf` Ruby plugin for known versions of Ruby:
|
||||
|
||||
```bash
|
||||
$ asdf list-all ruby | fzf
|
||||
```
|
||||
|
||||
I don't find the latest (`3.4`).
|
||||
|
||||
I need to update the plugin. A newer version of the plugin will know about
|
||||
newer Ruby versions.
|
||||
|
||||
```bash
|
||||
$ asdf plugin-update ruby
|
||||
```
|
||||
|
||||
Now, if I run the `list-all` command again, I'll find the version I'm looking
|
||||
for — `3.4.1`.
|
||||
|
||||
Now that `asdf` and I both know about the version to be installed, I can tell
|
||||
`asdf` to install it:
|
||||
|
||||
```bash
|
||||
$ asdf install ruby 3.4.1
|
||||
```
|
||||
|
||||
Now, if I check the current Ruby version, I'll see that it is still set to some
|
||||
other version.
|
||||
|
||||
```bash
|
||||
$ ruby --version
|
||||
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [x86_64-darwin22]
|
||||
```
|
||||
|
||||
I need to tell `asdf` to start using this newly installed version instead,
|
||||
either globally or locally.
|
||||
|
||||
```bash
|
||||
$ # globally
|
||||
$ asdf global ruby 3.4.1
|
||||
$ # or locally
|
||||
$ asdf local ruby 3.4.1
|
||||
```
|
||||
|
||||
And now I'm all set:
|
||||
|
||||
```bash
|
||||
$ asdf current ruby
|
||||
ruby 3.4.1 /Users/jbranchaud/.tool-versions
|
||||
|
||||
$ ruby --version
|
||||
ruby 3.4.1 (2024-12-25 revision 48d4efcb85) +PRISM [x86_64-darwin22]
|
||||
```
|
||||
43
ruby/refer-to-implicit-block-argument-with-it.md
Normal file
43
ruby/refer-to-implicit-block-argument-with-it.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Refer To Implicit Block Argument With It
|
||||
|
||||
One of the key features of the Ruby 3.4 release is the `it` implicit block
|
||||
argument.
|
||||
|
||||
The vast majority of inline blocks defined in Ruby code receive a single block
|
||||
argument. Typically we name and reference a block argument explictly like so:
|
||||
|
||||
```ruby
|
||||
items.map { |item| item * item }
|
||||
```
|
||||
|
||||
Ruby likes to cut away excess syntax when possible. To that end, the implicit
|
||||
`it` block argument has been added. This is an identifier we can reference in
|
||||
the context of a block and its value is the current
|
||||
|
||||
```ruby
|
||||
items = [1,2,3,4,5]
|
||||
|
||||
squares = items.map { it * it }
|
||||
|
||||
pp squares
|
||||
#=> [1, 4, 9, 16, 25]
|
||||
```
|
||||
|
||||
Note: we cannot mix numbered parameters (`_1`, `_2`) with the `it` parameter.
|
||||
If we do, we'll get the following error:
|
||||
|
||||
```ruby
|
||||
def method_using_block(a, b)
|
||||
yield(a, b) if block_given?
|
||||
end
|
||||
|
||||
puts method_using_block(4,5) { _2 ** _1 } #=> 625
|
||||
puts method_using_block(4,5) { _2 ** it }
|
||||
# it_block.rb:12: syntax error found (SyntaxError)
|
||||
# 10 |
|
||||
# 11 | puts method_using_block(4,5) { _2 ** _1 }
|
||||
# > 12 | ... it }
|
||||
# | ^~ `it` is not allowed when a numbered parameter is already used
|
||||
```
|
||||
|
||||
[source](https://docs.ruby-lang.org/en/3.4/NEWS_md.html)
|
||||
31
sqlite/explore-the-database-schema.md
Normal file
31
sqlite/explore-the-database-schema.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# 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)
|
||||
32
unix/manually-pass-two-git-files-to-delta.md
Normal file
32
unix/manually-pass-two-git-files-to-delta.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# 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.
|
||||
29
unix/see-where-asdf-gets-current-tool-version.md
Normal file
29
unix/see-where-asdf-gets-current-tool-version.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# See Where asdf Gets Current Tool Version
|
||||
|
||||
The other day I [installed the latest version of
|
||||
Ruby](ruby/install-latest-version-of-ruby-with-asdf.md) with `asdf`. I then set
|
||||
that version (`3.4.1`) as the global default. However, when I then ran `ruby
|
||||
--version`, I was getting a `3.2.x` version. I checked my current project's
|
||||
directory and there was no `.tool-versions` file, so it wasn't being set by my
|
||||
current directory.
|
||||
|
||||
`asdf` looks up the current chain of directories until it encounters a
|
||||
`.tool-versions` file, so it must have been finding one somewhere up there, but
|
||||
before it was getting to the _global_ `.tool-versions` file. But where?
|
||||
|
||||
The `asdf current` command can tell us for a specific tool what the current
|
||||
version it is set to and what file is giving that directive.
|
||||
|
||||
```bash
|
||||
asdf current ruby
|
||||
ruby 3.2.2 /Users/jbranchaud/code/.tool-versions
|
||||
```
|
||||
|
||||
As it turns out, I had a `.tool-versions` file in `$HOME/code` that was setting
|
||||
that `3.2.x` Ruby version.
|
||||
|
||||
I didn't want that directory controlling the Ruby version, so I removed `ruby`
|
||||
from that file. `asdf` was then able to traverse up to `$HOME/.tool-versions`
|
||||
for the global setting.
|
||||
|
||||
See `asdf help` for more details.
|
||||
48
workflow/break-justfile-into-separate-hidden-steps.md
Normal file
48
workflow/break-justfile-into-separate-hidden-steps.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Break Justfile Into Separate Hidden Steps
|
||||
|
||||
With `just` and a project's `justfile`, I can get a summary of the commands
|
||||
available to run against my project by running `just --list`. If I try to
|
||||
breakdown a complex, multi-step command into separate `just` commands, it will
|
||||
be nice for organization, but it will clutter the list output. I can mark
|
||||
specific commands as hidden or internal by preceding them with an underscore
|
||||
(`_`).
|
||||
|
||||
Here is a `justfile` from one of my projects that only lists a single command
|
||||
`setup` which itself is supported by three internal commands: `_check-brew`,
|
||||
`_install-deps`, and `_install-go-tools`.
|
||||
|
||||
```justfile
|
||||
# Install all required development dependencies
|
||||
setup: _check-brew _install-deps _install-go-tools
|
||||
|
||||
# Check if brew is installed
|
||||
_check-brew:
|
||||
#!/usr/bin/env bash
|
||||
if ! command -v brew &> /dev/null; then
|
||||
echo "Error: Homebrew is not installed"
|
||||
echo "Please install from https://brew.sh"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
brew_deps := '''
|
||||
go
|
||||
sqlite3
|
||||
'''
|
||||
|
||||
# Install brew dependencies
|
||||
_install-deps:
|
||||
#!/usr/bin/env bash
|
||||
deps=$(echo '{{brew_deps}}' | tr -s '[:space:]' ' ' | xargs)
|
||||
for pkg in $deps; do
|
||||
if ! brew list $pkg &>/dev/null; then
|
||||
echo "Installing $pkg..."
|
||||
brew install $pkg
|
||||
else
|
||||
echo "✓ $pkg already installed"
|
||||
fi
|
||||
done
|
||||
|
||||
# Install Go development tools
|
||||
_install-go-tools:
|
||||
go install github.com/pressly/goose/v3/cmd/goose@latest
|
||||
```
|
||||
22
workflow/show-linting-errors-in-zed.md
Normal file
22
workflow/show-linting-errors-in-zed.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# 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