mirror of
https://github.com/jbranchaud/til
synced 2026-01-07 09:08:01 +00:00
Compare commits
518 Commits
117856c2aa
...
copilot/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c58bfbb37f | ||
|
|
9b5af6a535 | ||
|
|
62d194f492 | ||
|
|
7a7a0faf94 | ||
|
|
d980514bff | ||
|
|
db26fc97c6 | ||
|
|
8094448877 | ||
|
|
883b3e6ee6 | ||
|
|
57c4954d6f | ||
|
|
86a7815a9f | ||
|
|
676038e992 | ||
|
|
01fd503a92 | ||
|
|
8b718aee4f | ||
|
|
88f49de7f3 | ||
|
|
9f9fce7835 | ||
|
|
65a4d0ef3d | ||
|
|
6c8a5eb36d | ||
|
|
fed722d7fe | ||
|
|
fbebc3e5ee | ||
|
|
83d55c420e | ||
|
|
8dbbfe0eda | ||
|
|
c38d9f090e | ||
|
|
bae3527baf | ||
|
|
53a0b88eff | ||
|
|
665c8f994f | ||
|
|
5f11b1665b | ||
|
|
ce5ff038c0 | ||
|
|
486a6ef5a9 | ||
|
|
c1ce559452 | ||
|
|
07c4aa86b7 | ||
|
|
a0c2a29a96 | ||
|
|
45b269abf1 | ||
|
|
44dc6f2b1f | ||
|
|
821a7e5c67 | ||
|
|
c0f20267bb | ||
|
|
50deb6175f | ||
|
|
d1f41884ce | ||
|
|
91149fe7cc | ||
|
|
e2eb31a4a9 | ||
|
|
113b7b2dfe | ||
|
|
16074c021f | ||
|
|
9e76753540 | ||
|
|
eb4dea611e | ||
|
|
6ef998b024 | ||
|
|
6e066ec72a | ||
|
|
060ce8262d | ||
|
|
96fd138837 | ||
|
|
a51d716e45 | ||
|
|
59de2fef0d | ||
|
|
fdd2461b75 | ||
|
|
e8c2e01d6f | ||
|
|
ed9cedc870 | ||
|
|
da585ec5a4 | ||
|
|
35d1a81ea7 | ||
|
|
d69fefe9f0 | ||
|
|
1cc612294e | ||
|
|
d79264395b | ||
|
|
2d5abd9cbf | ||
|
|
2efaf27066 | ||
|
|
6b4b2c588c | ||
|
|
e473fa781d | ||
|
|
5ce5eccb0a | ||
|
|
db4961a8eb | ||
|
|
ff227a39ed | ||
|
|
0d3975eb9c | ||
|
|
d171c3784b | ||
|
|
e6d00a94f3 | ||
|
|
0e934d8dd3 | ||
|
|
c30b17dd68 | ||
|
|
757e163c2e | ||
|
|
cf037f13f7 | ||
|
|
08fb235e81 | ||
|
|
95dc00d748 | ||
|
|
ece12aac76 | ||
|
|
9a6a40bdd6 | ||
|
|
4b4bd2350f | ||
|
|
5924edf4c0 | ||
|
|
5eb21b3aa2 | ||
|
|
5b3f1536fd | ||
|
|
ec0e84664f | ||
|
|
3912276599 | ||
|
|
d166ffac0b | ||
|
|
e8b953ba6d | ||
|
|
8613c21f41 | ||
|
|
2b5df03981 | ||
|
|
aef15d53b0 | ||
|
|
0c31fb6363 | ||
|
|
cb94142042 | ||
|
|
ae2974e3b8 | ||
|
|
0ed4d84bc6 | ||
|
|
3b7e3258fe | ||
|
|
d00796b054 | ||
|
|
8fecb0e863 | ||
|
|
14942c20d7 | ||
|
|
e901ae3b77 | ||
|
|
a4fee08596 | ||
|
|
6e518763c7 | ||
|
|
bb40353512 | ||
|
|
917f9e516e | ||
|
|
d8dfcce0fc | ||
|
|
0d173ccaaf | ||
|
|
8dd9f86b80 | ||
|
|
2bb8af2880 | ||
|
|
e16c2525be | ||
|
|
a55fff68e1 | ||
|
|
162a7ceea3 | ||
|
|
f578727349 | ||
|
|
4ba53dca7d | ||
|
|
571f465fe6 | ||
|
|
a547b9cee2 | ||
|
|
99ce5aee7b | ||
|
|
60b6aa40ad | ||
|
|
f97634a61e | ||
|
|
34ba60d313 | ||
|
|
3a178e901e | ||
|
|
db07125ba9 | ||
|
|
b6cf4ba0ad | ||
|
|
e4d695e465 | ||
|
|
5c9a3888fd | ||
|
|
22541826d6 | ||
|
|
b39ee94c90 | ||
|
|
efad7da916 | ||
|
|
ca3327bda3 | ||
|
|
595ac85f17 | ||
|
|
92d732c769 | ||
|
|
d6ebe52523 | ||
|
|
93398ab950 | ||
|
|
b1b2aa8982 | ||
|
|
6cbf1cb974 | ||
|
|
79faae1047 | ||
|
|
1316eb70ec | ||
|
|
ddf1c51fd9 | ||
|
|
60020d6e0e | ||
|
|
ef9f88f3c8 | ||
|
|
c8445c45a9 | ||
|
|
497b0ff3b7 | ||
|
|
64df6d16d7 | ||
|
|
7dac057246 | ||
|
|
8961c67026 | ||
|
|
4ff24a7336 | ||
|
|
2916fbc3b5 | ||
|
|
e3fc10edd8 | ||
|
|
4fe0817b2d | ||
|
|
fc74264040 | ||
|
|
2b38e1caf8 | ||
|
|
db6d18f086 | ||
|
|
84548b7a7f | ||
|
|
f9b966a0f1 | ||
|
|
871d555e95 | ||
|
|
cea3bc05b5 | ||
|
|
ee31f5b70d | ||
|
|
5b6a88b327 | ||
|
|
49ebb8dd78 | ||
|
|
6ff8f19d08 | ||
|
|
14e6635383 | ||
|
|
0a0a509827 | ||
|
|
bb331577ca | ||
|
|
0e0dcbf2b4 | ||
|
|
fe9b62a631 | ||
|
|
eb3369d296 | ||
|
|
6f47e2f057 | ||
|
|
409201611f | ||
|
|
77cc07a908 | ||
|
|
633c1fa0a5 | ||
|
|
96c394c198 | ||
|
|
0251157dc4 | ||
|
|
97c8701a5a | ||
|
|
1fd64e478a | ||
|
|
8ea123369b | ||
|
|
43c6e08b34 | ||
|
|
61fc021f52 | ||
|
|
46ad33df7e | ||
|
|
2028f6cb09 | ||
|
|
1f039a8958 | ||
|
|
c6eefeac98 | ||
|
|
31a0224fb7 | ||
|
|
aa71ff5f8b | ||
|
|
48278c4908 | ||
|
|
8b3ef4872c | ||
|
|
c2184a5ecf | ||
|
|
e2a8e815e9 | ||
|
|
c61ddcb326 | ||
|
|
7632664200 | ||
|
|
872a1d2a00 | ||
|
|
d52a126767 | ||
|
|
d9080cc583 | ||
|
|
654c65c8f6 | ||
|
|
138cab4fdc | ||
|
|
5592d4266d | ||
|
|
daf448c1a5 | ||
|
|
aaddc35fcd | ||
|
|
b575534d4e | ||
|
|
ae3ecbf72c | ||
|
|
1cf67b8f1a | ||
|
|
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 | ||
|
|
7274b3d563 | ||
|
|
1c9e12b96f | ||
|
|
a8ba753c92 | ||
|
|
77f3c6a43d | ||
|
|
bf6a10e2cd | ||
|
|
5083c8e9f1 | ||
|
|
a4c67c33a3 | ||
|
|
dce54bd689 | ||
|
|
f1cc33fe40 | ||
|
|
9dcd9daf0a | ||
|
|
9afe6503ec | ||
|
|
b376f32a67 | ||
|
|
3abfa92b64 | ||
|
|
d086d3b943 | ||
|
|
f64257f02c | ||
|
|
b329d36888 | ||
|
|
e0db60f6ce | ||
|
|
5c81ddc151 | ||
|
|
6af86bd407 | ||
|
|
98d8249cf1 | ||
|
|
7f1c243310 | ||
|
|
4f2399de13 | ||
|
|
7573119c59 | ||
|
|
86972f41cc | ||
|
|
93a663cc9c | ||
|
|
0c1dd29d8d | ||
|
|
b492a9d765 | ||
|
|
543a82730d | ||
|
|
877537228f | ||
|
|
1513611857 | ||
|
|
74514b462d | ||
|
|
484dec8e24 | ||
|
|
8574113dc6 | ||
|
|
1c4e37ed8a | ||
|
|
581aa1decb | ||
|
|
0c4795c1d2 | ||
|
|
9bbde247a5 | ||
|
|
36ca71bfb1 | ||
|
|
8a682e3a89 | ||
|
|
c7a38c8267 | ||
|
|
71d3e56b3d | ||
|
|
af3974d3fe | ||
|
|
adc6b2e903 | ||
|
|
9a6ebd4c6b | ||
|
|
6df0693804 | ||
|
|
507602ef0c | ||
|
|
18bdcc88b8 | ||
|
|
95115c7ebc | ||
|
|
4e859b93d2 | ||
|
|
23c20e99bf | ||
|
|
63bb627716 | ||
|
|
21385f4491 | ||
|
|
5b47326ab3 | ||
|
|
c16d80fd94 | ||
|
|
edf38308da | ||
|
|
dc7159c16c | ||
|
|
33f780a69f | ||
|
|
dfe9c002ee | ||
|
|
3e34636d80 | ||
|
|
dcef57d344 | ||
|
|
6580393b7a | ||
|
|
17d7f0933b | ||
|
|
e4abc56f4c | ||
|
|
43ea7acd74 | ||
|
|
d7d331b688 | ||
|
|
b743dc2ac0 | ||
|
|
4a72c63e42 | ||
|
|
431507fd0e | ||
|
|
fb153f35bf | ||
|
|
f4ba6a9ef7 | ||
|
|
0dee39d3c5 | ||
|
|
5ebdd9a1a9 | ||
|
|
33c5cd748f | ||
|
|
ff515c8d6a | ||
|
|
fad36e0691 | ||
|
|
4d1d8e7134 | ||
|
|
567637497c | ||
|
|
028b76ba6b | ||
|
|
1934c8f63e | ||
|
|
e5a003dbaf | ||
|
|
ab9d2b5bf6 | ||
|
|
aa00c55b06 | ||
|
|
cc858382d8 | ||
|
|
dbb8c585c1 | ||
|
|
f25064031f | ||
|
|
cfbe640eb0 | ||
|
|
bf04dfcca5 | ||
|
|
24b1b02d52 | ||
|
|
8ef2cfdc69 | ||
|
|
02086e7115 | ||
|
|
0ecc41bd29 | ||
|
|
569220e734 | ||
|
|
594ec08636 | ||
|
|
475f125f4b | ||
|
|
ba6492d46e | ||
|
|
3ac3014659 | ||
|
|
4b6833c437 | ||
|
|
fc93ecfed4 | ||
|
|
a07a19e6d9 | ||
|
|
951a2f04ad | ||
|
|
6b9ec224b8 | ||
|
|
c33be14ec2 | ||
|
|
2922bbdd6a | ||
|
|
2e72ec1160 | ||
|
|
baab5738e7 | ||
|
|
191c9d6d9d | ||
|
|
25b5677260 | ||
|
|
df3492d4ef | ||
|
|
bd49b31bb0 | ||
|
|
4ff1a381d1 | ||
|
|
0bfeb0e236 | ||
|
|
e91b163571 | ||
|
|
84e2c9c6f4 | ||
|
|
39614e975e | ||
|
|
44e626a086 | ||
|
|
48d2ecffa0 | ||
|
|
6fb3b95ade | ||
|
|
51880975d4 | ||
|
|
03980ab291 | ||
|
|
bef492fe3a | ||
|
|
36004e6d20 | ||
|
|
18e52b9dbb | ||
|
|
01c9c0d19b | ||
|
|
c06bb2ea7b | ||
|
|
7dad6508d0 | ||
|
|
5bcdbbb3c7 | ||
|
|
41f5b526d2 | ||
|
|
e974a184c6 | ||
|
|
c832b9a70d | ||
|
|
42854fdc38 | ||
|
|
eff2c548cd | ||
|
|
1b229b39f1 | ||
|
|
2543651ec0 | ||
|
|
f3d7cf8a06 | ||
|
|
72089e11db | ||
|
|
b766f20012 | ||
|
|
505220d9de | ||
|
|
9684c6a6db | ||
|
|
3b0d76e805 | ||
|
|
b6c8192a04 | ||
|
|
c2f30615c3 | ||
|
|
f54eab20e7 | ||
|
|
b2ddce62fd | ||
|
|
63a57c6bdd | ||
|
|
87d116a4cd | ||
|
|
71598d647b | ||
|
|
11859a096f | ||
|
|
6096f5af10 | ||
|
|
b6e5ea4c03 | ||
|
|
74a3dd3c3a | ||
|
|
7208fad280 | ||
|
|
0b994346fe | ||
|
|
daa0590684 | ||
|
|
2d10ade553 | ||
|
|
d1f83edea7 | ||
|
|
a05840633f | ||
|
|
22fde22447 | ||
|
|
c4e349f518 | ||
|
|
1b10e393d9 | ||
|
|
c20d19b024 | ||
|
|
8394d5bfaa | ||
|
|
5412e2e8b1 | ||
|
|
18f3159626 | ||
|
|
b3eb0f34d1 | ||
|
|
9a9cebaa11 | ||
|
|
24278989d8 | ||
|
|
d0690a3b5a | ||
|
|
00d614a106 | ||
|
|
cc1275aec5 | ||
|
|
9a237572aa | ||
|
|
5e68fb8c64 | ||
|
|
a83ce7cc10 | ||
|
|
a61a22f707 | ||
|
|
fe8d06b94a | ||
|
|
80eff6f897 | ||
|
|
d5470db75b | ||
|
|
5a3ef11820 | ||
|
|
57eb2d73ae | ||
|
|
613f1f8e37 | ||
|
|
e86c36eff4 | ||
|
|
4e4b9b3d25 | ||
|
|
030a3f9791 | ||
|
|
d02d6e006b | ||
|
|
8100432fad | ||
|
|
f6882f9fec | ||
|
|
11d32fad0d | ||
|
|
63097d0feb | ||
|
|
83fddacd29 | ||
|
|
bf814b4833 | ||
|
|
0c8cb341d6 | ||
|
|
5f20cd569e | ||
|
|
678f327cb9 | ||
|
|
ff2f3635cb | ||
|
|
fefada8de8 | ||
|
|
dd13203ade | ||
|
|
c591565058 | ||
|
|
851af3776d | ||
|
|
6f2ae93e45 | ||
|
|
f03bf4cb70 | ||
|
|
4edc43e4bb | ||
|
|
5bcd67946c | ||
|
|
778555636e | ||
|
|
b1994cfe21 | ||
|
|
52d31fa41b | ||
|
|
8789d89e31 | ||
|
|
5d61f4c9d7 | ||
|
|
5811268d3f | ||
|
|
d472a900c7 | ||
|
|
94afc729f9 | ||
|
|
ee3c671c47 | ||
|
|
9bcbcbc7c0 | ||
|
|
a726b2ec30 | ||
|
|
7387786343 | ||
|
|
5e63e420bc | ||
|
|
129cd11940 | ||
|
|
c1d2f64558 | ||
|
|
62ddc97d1b | ||
|
|
2dcb0dd5e1 | ||
|
|
5f0d29885b | ||
|
|
66fd1e6c19 | ||
|
|
fba4cb171f | ||
|
|
fb33d03d5a | ||
|
|
aaa0edee98 | ||
|
|
67e8996f99 | ||
|
|
a3927ee02e | ||
|
|
544d580ede | ||
|
|
6fad82d653 | ||
|
|
cee26f173d | ||
|
|
5cb7d74933 | ||
|
|
127329e22c | ||
|
|
5a8b381de8 | ||
|
|
32706827e9 | ||
|
|
5445916004 | ||
|
|
ccc047549b | ||
|
|
cc2d84ac19 | ||
|
|
c185ac18c5 | ||
|
|
3c7899c67d | ||
|
|
570dbdb745 | ||
|
|
b3a6c52c24 | ||
|
|
dcdfae4a1e | ||
|
|
7da6d33f9d | ||
|
|
72e3d551f3 | ||
|
|
6f99af3ec5 | ||
|
|
4840461afa | ||
|
|
fa20b1aa66 | ||
|
|
00481bb71a | ||
|
|
7e2b60e2f9 | ||
|
|
951b7f953f | ||
|
|
c0fdfebc0c | ||
|
|
3cfd11f1e6 | ||
|
|
85db06d051 | ||
|
|
96d4572786 | ||
|
|
8bac2d832d | ||
|
|
629d8b7f7b | ||
|
|
746957ca75 | ||
|
|
34b38244c4 | ||
|
|
0ec717b64c | ||
|
|
fd947a5cf1 | ||
|
|
c2e4a29629 | ||
|
|
03604ee03a | ||
|
|
fd3da2c985 | ||
|
|
ab60deea73 | ||
|
|
10554905ad | ||
|
|
54b71bbf24 | ||
|
|
80f502f661 | ||
|
|
a09ec39aa1 | ||
|
|
5a72dcfb95 | ||
|
|
12178cf153 | ||
|
|
f7313218c1 | ||
|
|
0dda4ab55a | ||
|
|
6bc8897e1e | ||
|
|
fb79ce1f14 | ||
|
|
198e40fc01 | ||
|
|
7212785cb3 | ||
|
|
ead1ba4c12 | ||
|
|
0811ee8c1b | ||
|
|
4ee289e41c | ||
|
|
10cc283948 | ||
|
|
d7e7350a24 | ||
|
|
9732f5a18a | ||
|
|
2fcf2829c5 | ||
|
|
e2cbec17bf | ||
|
|
15dd720cee | ||
|
|
93d5e69f97 | ||
|
|
e80721bb6f | ||
|
|
678a533865 | ||
|
|
f8dec09218 | ||
|
|
d8c280513a | ||
|
|
16d8ad7849 | ||
|
|
b589f1ced8 | ||
|
|
d26be13cd4 | ||
|
|
b8f8310421 | ||
|
|
55ee820786 | ||
|
|
06b0db1209 | ||
|
|
29656fd7d6 | ||
|
|
a92b6a3848 | ||
|
|
e7457a9e54 |
5
.gitmodules
vendored
Normal file
5
.gitmodules
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
[submodule "notes"]
|
||||
path = notes
|
||||
url = git@github.com:jbranchaud/til-notes-private.git
|
||||
branch = main
|
||||
ignore = all
|
||||
12
.vimrc
12
.vimrc
@@ -9,3 +9,15 @@ function! CountTILs()
|
||||
endfunction
|
||||
|
||||
nnoremap <leader>c :call CountTILs()<cr>
|
||||
|
||||
augroup DisableMarkdownFormattingForTILReadme
|
||||
autocmd!
|
||||
autocmd BufRead ~/code/til/README.md autocmd! Format
|
||||
augroup END
|
||||
|
||||
" local til_readme_group = vim.api.nvim_create_augroup('DisableMarkdownFormattingForTILReadme', { clear = true })
|
||||
" vim.api.nvim_create_autocmd('BufRead', {
|
||||
" command = 'autocmd! Format',
|
||||
" group = til_readme_group,
|
||||
" pattern = vim.fn.expand '~/code/til/README.md',
|
||||
" })
|
||||
|
||||
79
Taskfile.yml
Normal file
79
Taskfile.yml
Normal file
@@ -0,0 +1,79 @@
|
||||
version: '3'
|
||||
|
||||
vars:
|
||||
NOTES_DIR: notes
|
||||
NOTES_FILE: '{{.NOTES_DIR}}/NOTES.md'
|
||||
EDITOR: '{{.EDITOR | default "nvim"}}'
|
||||
|
||||
tasks:
|
||||
default:
|
||||
desc: Show available commands
|
||||
cmds:
|
||||
- task --list
|
||||
|
||||
notes:
|
||||
desc: Interactive picker for notes tasks
|
||||
cmds:
|
||||
- |
|
||||
TASK=$(task --list | grep "^\* notes:" | sed 's/^\* notes://' | sed 's/\s\+/ - /' | fzf --prompt="Select notes task: " --height=40% --reverse) || true
|
||||
if [ -n "$TASK" ]; then
|
||||
TASK_NAME=$(echo "$TASK" | awk '{print $1}' | sed 's/:$//')
|
||||
task notes:$TASK_NAME
|
||||
fi
|
||||
interactive: true
|
||||
silent: true
|
||||
|
||||
notes:edit:
|
||||
desc: All-in-one edit, commit, and push notes
|
||||
cmds:
|
||||
- task notes:open
|
||||
- task notes:push
|
||||
|
||||
notes:sync:
|
||||
desc: Sync latest changes from the notes submodule
|
||||
cmds:
|
||||
- git submodule update --remote {{.NOTES_DIR}}
|
||||
- cd {{.NOTES_DIR}} && git checkout main
|
||||
silent: false
|
||||
|
||||
notes:open:
|
||||
desc: Opens NOTES.md (syncs latest changes first) in default editor
|
||||
deps: [notes:sync]
|
||||
cmds:
|
||||
- $EDITOR {{.NOTES_FILE}}
|
||||
interactive: true
|
||||
|
||||
notes:push:
|
||||
desc: Commit and push changes to notes submodule
|
||||
dir: '{{.NOTES_DIR}}'
|
||||
cmds:
|
||||
- git add NOTES.md
|
||||
- git commit -m "Update notes - $(date '+%Y-%m-%d %H:%M')"
|
||||
- git pull --rebase
|
||||
- git push
|
||||
status:
|
||||
- git diff --exit-code NOTES.md
|
||||
silent: false
|
||||
|
||||
notes:status:
|
||||
desc: Check status of notes submodule
|
||||
dir: '{{.NOTES_DIR}}'
|
||||
cmds:
|
||||
- git status
|
||||
|
||||
notes:pull:
|
||||
desc: Pull latest changes (alias for sync)
|
||||
cmds:
|
||||
- task notes:sync
|
||||
|
||||
notes:diff:
|
||||
desc: Show uncommitted changes in notes
|
||||
dir: '{{.NOTES_DIR}}'
|
||||
cmds:
|
||||
- git diff NOTES.md
|
||||
|
||||
notes:log:
|
||||
desc: Show recent commit history for notes
|
||||
dir: '{{.NOTES_DIR}}'
|
||||
cmds:
|
||||
- git log --oneline -10
|
||||
33
ansible/loop-over-a-list-of-dictionaries.md
Normal file
33
ansible/loop-over-a-list-of-dictionaries.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Loop Over A List Of Dictionaries
|
||||
|
||||
Ansible's `loop` can iterate over a list of dictionaries in a task. That task
|
||||
will be evaluated for each `item` in that list. Since each `item` is a
|
||||
dictonary, we can access the fields on the `item` directory with dot notation —
|
||||
`item.name`.
|
||||
|
||||
Here is what this would look like for a task that is setting up authorized SSH
|
||||
keys.
|
||||
|
||||
```yaml
|
||||
---
|
||||
- hosts: all
|
||||
vars:
|
||||
dev_users:
|
||||
- name: alice
|
||||
ssh_key_url: https://github.com/dev1.keys
|
||||
- name: bob
|
||||
ssh_key_url: https://github.com/dev2.keys
|
||||
tasks:
|
||||
- name: Set authorized keys taken from url
|
||||
ansible.posix.authorized_key:
|
||||
user: "{{ item.name }}"
|
||||
state: present
|
||||
key: "{{ item.ssh_key_url }}"
|
||||
loop: "{{ dev_users }}"
|
||||
```
|
||||
|
||||
Notice the `loop` over the `dev_users` variable gives us access to an `item` in
|
||||
the task. Because each `item` has a `name` and an `ssh_key_url`, we can access
|
||||
those fields in the task.
|
||||
|
||||
[source](https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_loops.html#standard-loops)
|
||||
56
astro/generate-types-for-a-content-collection.md
Normal file
56
astro/generate-types-for-a-content-collection.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Generate Types For A Content Collection
|
||||
|
||||
Let's say I'm using Astro to publish posts via markdown. One of the best ways
|
||||
to do that is as a _Content Collection_. The posts will live in `src/content`
|
||||
probably under a `posts` directory. Plus a config file will define the
|
||||
collection and specify validations for the frontmatter.
|
||||
|
||||
```typescript
|
||||
// src/content/config.ts
|
||||
import { defineCollection, z } from 'astro:content';
|
||||
|
||||
const postsCollection = defineCollection({
|
||||
schema: z.object({
|
||||
title: z.string(),
|
||||
description: z.string(),
|
||||
tags: z.array(z.string())
|
||||
})
|
||||
});
|
||||
|
||||
export const collections = {
|
||||
'posts': postsCollection,
|
||||
};
|
||||
```
|
||||
|
||||
When I first add this to my project and get the collection, it won't know what
|
||||
the types are.
|
||||
|
||||
```astro
|
||||
---
|
||||
import { getCollection } from "astro:content";
|
||||
|
||||
export async function getStaticPaths() {
|
||||
const blogEntries = await getCollection("posts");
|
||||
// ^^^ any
|
||||
|
||||
return blogEntries.map((entry) => ({
|
||||
params: { slug: entry.slug },
|
||||
props: { entry },
|
||||
}));
|
||||
}
|
||||
---
|
||||
```
|
||||
|
||||
I can tell Astro to generate a fresh set of types for things like content
|
||||
collections by running the [`astro sync`
|
||||
command](https://docs.astro.build/en/reference/cli-reference/#astro-sync).
|
||||
|
||||
```bash
|
||||
$ npm run astro sync
|
||||
```
|
||||
|
||||
This updates auto-generated files under the `.astro` directory which get pulled
|
||||
in to your project's `env.d.ts` file.
|
||||
|
||||
All of these types will also be synced anytime I run `astro dev`, `astro
|
||||
build`, or `astro check`.
|
||||
53
astro/markdown-files-are-of-type-markdown-instance.md
Normal file
53
astro/markdown-files-are-of-type-markdown-instance.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Markdown Files Are Of Type MarkdownInstance
|
||||
|
||||
One of the things Astro excels at is rendering markdown files as HTML pages in
|
||||
your site. And at some point we'll want to access a listing of those markdown
|
||||
files in order to do something like display a list of them on an index page.
|
||||
For that, we'll use
|
||||
[`Astro.glob()`](https://docs.astro.build/en/reference/api-reference/#astroglob).
|
||||
|
||||
```typescript
|
||||
---
|
||||
const allPosts = await Astro.glob("../posts/*.md");
|
||||
---
|
||||
|
||||
<ul>
|
||||
{allPosts.map(post => {
|
||||
return <Post title={post.frontmatter.title} slug={post.frontmatter.slug} />
|
||||
})}
|
||||
</ul>
|
||||
```
|
||||
|
||||
This looks great, but we'll run into a type error on that first line:
|
||||
`'allPosts' implicitly has type 'any'`. We need to declare the type
|
||||
of these post instances that are being read-in by Astro.
|
||||
|
||||
These are of [type
|
||||
`MarkdownInstance`](https://docs.astro.build/en/reference/api-reference/#markdown-files).
|
||||
That's a generic though, so we need to tell it a bit more about the shape of a
|
||||
post.
|
||||
|
||||
```typescript
|
||||
import type { MarkdownInstance } from "astro";
|
||||
|
||||
export type BarePost = {
|
||||
layout: string;
|
||||
title: string;
|
||||
slug: string;
|
||||
tags: string[];
|
||||
};
|
||||
|
||||
export type Post = MarkdownInstance<BarePost>;
|
||||
```
|
||||
|
||||
We can then update that first line:
|
||||
|
||||
```typescript
|
||||
const allPosts: Post[] = await Astro.glob("../posts/*.md");
|
||||
```
|
||||
|
||||
Alternatively, you can specify the generic on `glob`:
|
||||
|
||||
```typescript
|
||||
const allPosts = await Astro.glob<BarePost>("../posts/*.md");
|
||||
```
|
||||
30
aws/aws-cli-requires-groff-executable.md
Normal file
30
aws/aws-cli-requires-groff-executable.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# AWS CLI Requires Groff Executable
|
||||
|
||||
I have the AWS CLI installed on this machine, but when I went to run certain
|
||||
commands like `aws logs tail my_log_group` or even `aws logs tail help`, I'd
|
||||
get the following error:
|
||||
|
||||
```
|
||||
$ aws logs tail help
|
||||
|
||||
Could not find executable named 'groff'
|
||||
```
|
||||
|
||||
This may only be an issue on MacOS Ventura for older versions of the CLI, per
|
||||
[this PR](https://github.com/aws/aws-cli/pull/7413):
|
||||
|
||||
> The CLI's help commands are currently broken on macOS Ventura because Ventura has replaced groff with mandoc. This PR fixes the issue by falling back on mandoc if groff doesn't exist in the path.
|
||||
|
||||
There are two ways of dealing with this. One would be to install the missing
|
||||
dependency, [`groff`](https://www.gnu.org/software/groff/):
|
||||
|
||||
```bash
|
||||
$ brew install groff
|
||||
```
|
||||
|
||||
The other is to update the AWS CLI to one that falls back to `mandoc`.
|
||||
Depending on how you originally installed the AWS CLI, you can either [follow
|
||||
their official install/upgrade
|
||||
instructions](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html),
|
||||
`pip install --upgrade awscli`, or upgrade view homebrew (`brew upgrade
|
||||
awscli`).
|
||||
46
aws/find-and-follow-server-logs.md
Normal file
46
aws/find-and-follow-server-logs.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Find And Follow Server Logs
|
||||
|
||||
Let's say you are authenticated with the AWS CLI and have the appropriate
|
||||
CloudWatch permissions. You have a few services running in production with
|
||||
associated logs. One of those is a Rails server.
|
||||
|
||||
We want to run `aws logs tail`, but first we check how that command works.
|
||||
|
||||
```bash
|
||||
$ aws logs tail help
|
||||
```
|
||||
|
||||
We see a bunch of options, but the only required one is `group_name` ("The name
|
||||
of the CloudWatch Logs group."). We may also notice the `--follow` flag which
|
||||
we'll want to use as well to keep incoming logs flowing.
|
||||
|
||||
We need to determine the log group name for the Rails server. We can do that
|
||||
from the CLI as well (no need to dig into the web UI).
|
||||
|
||||
```bash
|
||||
$ aws logs describe-log-groups
|
||||
|
||||
{
|
||||
"logGroups": [
|
||||
{
|
||||
"logGroupName": "/aws/codebuild/fc-rails-app-abcefg-123456",
|
||||
"creationTime": 1739476650823,
|
||||
"metricFilterCount": 0,
|
||||
"arn": "arn:aws:logs:us-east-2:123456789:log-group:/aws/codebuild/fc-rails-app-abcefg-123456:*",
|
||||
"storedBytes": 65617,
|
||||
"logGroupClass": "STANDARD",
|
||||
"logGroupArn": "arn:aws:logs:us-east-2:123456789:log-group:/aws/codebuild/fc-rails-app-abcefg-123456"
|
||||
},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Because the group name is descriptive enough, we can find the log group we are
|
||||
interested in: `/aws/codebuild/fc-rails-app-abcefg-123456`.
|
||||
|
||||
Now we know what we want to `tail`.
|
||||
|
||||
```bash
|
||||
$ aws logs tail /aws/codebuild/fc-rails-app-abcefg-123456 --follow
|
||||
```
|
||||
29
aws/list-rds-snapshots-with-matching-identifier-prefix.md
Normal file
29
aws/list-rds-snapshots-with-matching-identifier-prefix.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# List RDS Snapshots With Matching Identifier Prefix
|
||||
|
||||
I'm working on a script that manually creates a snapshot which it will then
|
||||
restore to a temporary database that I can scrub and dump. The snapshots that
|
||||
this script takes are _manual_ and they are named with identifiers that have a
|
||||
defining prefix (`dev-snapshot-`). Besides the few snapshots created by this
|
||||
script, there are tons of automated snapshots that RDS creates for
|
||||
backup/recovery purposes.
|
||||
|
||||
I want to list any snapshots that have been created by the script. I can do
|
||||
this with the `describe-db-snapshots` command and some filters.
|
||||
|
||||
```bash
|
||||
$ aws rds describe-db-snapshots \
|
||||
--snapshot-type manual \
|
||||
--query "DBSnapshots[?starts_with(DBSnapshotIdentifier, 'dev-snapshot-')].DBSnapshotIdentifier" \
|
||||
--no-cli-pager
|
||||
|
||||
[
|
||||
"dev-snapshot-20250327-155355"
|
||||
]
|
||||
```
|
||||
|
||||
There are two key pieces. The `--snapshot-type manual` filter excludes all
|
||||
those automated snapshots. The `--query` both filters to any snapshots whose
|
||||
identifier `?starts_with` the prefix `dev-snapshot-` and then refines the
|
||||
output to just the `DBSnapshotIdentifier` instead of the entire JSON object.
|
||||
|
||||
[source](https://docs.aws.amazon.com/cli/latest/reference/rds/describe-db-snapshots.html)
|
||||
49
aws/output-cli-results-in-different-formats.md
Normal file
49
aws/output-cli-results-in-different-formats.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Output CLI Results In Different Formats
|
||||
|
||||
The AWS CLI can output the results of commands in three different formats.
|
||||
|
||||
- Text
|
||||
- JSON
|
||||
- Table
|
||||
|
||||
The _default_ output format for my AWS CLI is currently configured to `json`.
|
||||
|
||||
```bash
|
||||
$ aws configure get output
|
||||
json
|
||||
```
|
||||
|
||||
I can either accept the default or I can override it with the `--output` flag.
|
||||
|
||||
```bash
|
||||
$ aws rds describe-db-instances \
|
||||
--query 'DBInstances[*].Endpoint' \
|
||||
--no-cli-pager
|
||||
[
|
||||
{
|
||||
"Address": "fc-database-abcefg-ab1c23de.asdfgh4zxcvb.us-east-2.rds.amazonaws.com",
|
||||
"Port": 5432,
|
||||
"HostedZoneId": "A1BCDE2FG345H6"
|
||||
}
|
||||
]
|
||||
|
||||
$ aws rds describe-db-instances \
|
||||
--query 'DBInstances[*].Endpoint' \
|
||||
--no-cli-pager \
|
||||
--output table
|
||||
----------------------------------------------------------------------------------------------------
|
||||
| DescribeDBInstances |
|
||||
+-----------------------------------------------------------------------+-----------------+--------+
|
||||
| Address | HostedZoneId | Port |
|
||||
+-----------------------------------------------------------------------+-----------------+--------+
|
||||
| fc-database-abcefg-ab1c23de.asdfgh4zxcvb.us-east-2.rds.amazonaws.com | A1BCDE2FG345H6 | 5432 |
|
||||
+-----------------------------------------------------------------------+-----------------+--------+
|
||||
|
||||
$ aws rds describe-db-instances \
|
||||
--query 'DBInstances[*].Endpoint' \
|
||||
--no-cli-pager \
|
||||
--output text
|
||||
fc-database-abcefg-ab1c23de.asdfgh4zxcvb.us-east-2.rds.amazonaws.com A1BCDE2FG345H6 5432
|
||||
```
|
||||
|
||||
[source](https://docs.aws.amazon.com/cli/v1/userguide/cli-usage-output-format.html)
|
||||
50
aws/ssh-into-an-ecs-container.md
Normal file
50
aws/ssh-into-an-ecs-container.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# SSH Into An ECS Container
|
||||
|
||||
In [Connect To Production Rails Console on AWS /
|
||||
Flightcontrol](https://www.visualmode.dev/connect-to-production-rails-console-aws-flightcontrol),
|
||||
I went into full detail about how to access `rails console` for a production
|
||||
Rails app running in an ECS container.
|
||||
|
||||
A big part of that process was establishing an SSH connection to the ECS container.
|
||||
|
||||
To do that, I need to know my region, container ID, and task ID. I can get the
|
||||
first two by listing my clusters and finding the cluster/container that houses
|
||||
the Rails app.
|
||||
|
||||
```bash
|
||||
$ aws ecs list-clusters
|
||||
{
|
||||
"clusterArns": [
|
||||
"arn:aws:ecs:us-east-2:123:cluster/rails-app-abc123"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The region then is `us-east-2` and the container ID is `rails-app-abc123`.
|
||||
|
||||
I can use that to find the task ID:
|
||||
|
||||
```bash
|
||||
$ aws ecs list-tasks --region us-east-2 --cluster rails-app-abc123
|
||||
{
|
||||
"taskArns": [
|
||||
"arn:aws:ecs:us-east-2:123:task/rails-app-abc123/8526b3191d103bb1ff90c65a655ad004"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The task ID is the final portion of the URL:
|
||||
`8526b3191d103bb1ff90c65a655ad004`.
|
||||
|
||||
Putting this all together I can SSH into the ECS container with a bash profile
|
||||
like so:
|
||||
|
||||
```bash
|
||||
$ aws ecs execute-command \
|
||||
--region us-east-2 \
|
||||
--cluster rails-app-abc123 \
|
||||
--container rails-app-abc123 \
|
||||
--task 8526b3191d103bb1ff90c65a655ad004 \
|
||||
--interactive \
|
||||
--command "/bin/bash"
|
||||
```
|
||||
38
aws/turn-off-output-pager-for-a-command.md
Normal file
38
aws/turn-off-output-pager-for-a-command.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Turn Off Output Pager For A Command
|
||||
|
||||
It is not uncommon for an AWS CLI command to return a ton of output. When that
|
||||
happens, it is nice that the results end up in pager program (like `less`)
|
||||
where you can search and review them, copy a value of interest, and then exit.
|
||||
The pager prevents that wall of output from cluttering your terminal history.
|
||||
|
||||
However, sometimes I am running a command that I know is going to return a
|
||||
small result. I'd rather have the results go to stdout where I can see them in
|
||||
the terminal history rather than to an ephemeral pager.
|
||||
|
||||
For that situation I can tack on the `--no-cli-pager` flag.
|
||||
|
||||
```bash
|
||||
$ aws rds describe-db-instances \
|
||||
--query 'DBInstances[*].EngineVersion' \
|
||||
--output json \
|
||||
--no-cli-pager
|
||||
|
||||
[
|
||||
"13.15",
|
||||
"16.8"
|
||||
]
|
||||
```
|
||||
|
||||
Here I've asked the AWS CLI to tell me the engine versions of all my RDS
|
||||
Postgres databases. Because I know the results are only going to include a
|
||||
couple results for my couple of DBs, I'd like to skip the pager —
|
||||
`--no-cli-pager`.
|
||||
|
||||
Though I think it is better to do this on a case by case basis, it is also
|
||||
possible to turn off the pager via the CLI configuration file.
|
||||
|
||||
```bash
|
||||
$ aws configure set cli_pager ""
|
||||
```
|
||||
|
||||
[source](https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-pagination.html#cli-usage-pagination-clientside)
|
||||
37
aws/use-specific-aws-profile-with-cli.md
Normal file
37
aws/use-specific-aws-profile-with-cli.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Use Specific AWS Profile With CLI
|
||||
|
||||
I have multiple AWS profiles authenticated with the AWS CLI. For some projects
|
||||
I need to use the `default` one and for others I need to use the other.
|
||||
|
||||
First, I can list the available profiles like so:
|
||||
|
||||
```bash
|
||||
$ aws configure list-profiles
|
||||
default
|
||||
dev-my-app
|
||||
```
|
||||
|
||||
For one-off commands I can specify the profile for any AWS CLI command using
|
||||
the `--profile` flag.
|
||||
|
||||
```bash
|
||||
$ aws ecs list-clusters --profile josh-visualmode
|
||||
```
|
||||
|
||||
However, I don't want to have to specify that flag every time when I'm working
|
||||
on a specific project. Instead I can specify the profile with an environment
|
||||
variable. The [`direnv`](https://direnv.net/) tool is a great way to do this on
|
||||
a per-project / per-directory basis.
|
||||
|
||||
I can create or update the `.envrc` file (assuming I have `direnv` installed)
|
||||
adding the following line (and re-allowing the changed file):
|
||||
|
||||
```
|
||||
# .envrc
|
||||
export AWS_PROFILE=dev-my-app
|
||||
```
|
||||
|
||||
Now, any AWS command I issue from that directory or its subdirectories will use
|
||||
that profile by default.
|
||||
|
||||
[source](https://docs.aws.amazon.com/cli/v1/userguide/cli-configure-files.html#cli-configure-files-using-profiles)
|
||||
40
brew/clean-up-your-brew-installations.md
Normal file
40
brew/clean-up-your-brew-installations.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Clean Up Your Brew Installations
|
||||
|
||||
Over time as you upgrade brew-installed programs and make changes to your
|
||||
`Brewfile`, your machine will have artifacts left behind that you no longer
|
||||
need.
|
||||
|
||||
Periodically, it is good to clean things up.
|
||||
|
||||
First, you can get a summary of stale and outdated files that brew has
|
||||
installed. Use the `--dry-run` flag.
|
||||
|
||||
```bash
|
||||
$ brew cleanup --dry-run
|
||||
```
|
||||
|
||||
If you feel good about what you see in the output, then give things a clean.
|
||||
|
||||
```bash
|
||||
$ brew cleanup
|
||||
```
|
||||
|
||||
Second, if you are using a `Brewfile` to manage what `brew` installs, then you
|
||||
can instruct `brew` to uninstall any dependencies that aren't specified in that
|
||||
file.
|
||||
|
||||
By default it operates as a dry run and the `--force` flag will be needed to
|
||||
actually do the cleanup. And specify the filename if it doesn't match the
|
||||
default of `Brewfile`.
|
||||
|
||||
```bash
|
||||
$ brew bundle cleanup --file=Brewfile.personal
|
||||
```
|
||||
|
||||
If the output looks good, then force the cleanup:
|
||||
|
||||
```bash
|
||||
$ brew bundle cleanup --force --file=Brewfile.personal
|
||||
```
|
||||
|
||||
See `brew cleanup --help` and `brew bundle --help` for more details.
|
||||
28
brew/configure-brew-environment-variables.md
Normal file
28
brew/configure-brew-environment-variables.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Configure Brew Environment Variables
|
||||
|
||||
The `brew` CLI can be configured with a ton of different environment variables.
|
||||
A full listing of those can be found in the [Environment section of their
|
||||
docs](https://docs.brew.sh/Manpage#environment).
|
||||
|
||||
If you want to change the defaults of any of those values, you can either set
|
||||
them directly in your environment:
|
||||
|
||||
```bash
|
||||
$ set HOMEBREW_BAT=1
|
||||
```
|
||||
|
||||
Or you can set them in a more dedicated place like one of Homebrew's
|
||||
environment files. There are a couple possible locations for these files. I
|
||||
prefer to use `$HOME/.homebrew/brew.env` (i.e. `~/.homebrew/brew.env`).
|
||||
|
||||
```
|
||||
HOMEBREW_BAT=1
|
||||
```
|
||||
|
||||
This file and directly likely don't exist, so you may have to set them up the
|
||||
first time:
|
||||
|
||||
```
|
||||
$ mkdir $HOME/.homebrew
|
||||
$ touch $HOME/.homebrew/brew.env
|
||||
```
|
||||
48
brew/export-list-of-everything-installed-by-brew.md
Normal file
48
brew/export-list-of-everything-installed-by-brew.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Export List Of Everything Installed By Brew
|
||||
|
||||
If you're on a Mac using Homebrew to install various tools and utilities, there
|
||||
may come a time when you want a listing of what is installed.
|
||||
|
||||
Run this command:
|
||||
|
||||
```bash
|
||||
$ brew bundle dump
|
||||
```
|
||||
|
||||
It may take 10 or so seconds. When it is done, you'll have a `Brewfile` in your
|
||||
current directory.
|
||||
|
||||
Open it up and you'll see a bunch of lines like the following:
|
||||
|
||||
```
|
||||
tap "heroku/brew"
|
||||
tap "homebrew/bundle"
|
||||
tap "homebrew/services"
|
||||
tap "mongodb/brew"
|
||||
tap "planetscale/tap"
|
||||
tap "stripe/stripe-cli"
|
||||
brew "asdf"
|
||||
brew "bat"
|
||||
brew "direnv"
|
||||
brew "entr"
|
||||
brew "exa"
|
||||
brew "fd"
|
||||
brew "ffmpeg"
|
||||
brew "fx"
|
||||
brew "fzf"
|
||||
brew "gcc"
|
||||
brew "gh"
|
||||
brew "planetscale/tap/pscale"
|
||||
brew "stripe/stripe-cli/stripe"
|
||||
cask "1password-cli"
|
||||
vscode "ms-playwright.playwright"
|
||||
vscode "ms-vsliveshare.vsliveshare"
|
||||
vscode "prisma.prisma"
|
||||
```
|
||||
|
||||
Notice there are `tap`, `brew`, `cask`, and even `vscode` directives.
|
||||
|
||||
This is a file you could export and then run on a 'new' machine to install all
|
||||
the programs you're used to having available on your current machine.
|
||||
|
||||
[source](https://danmunoz.com/setting-up-a-new-computer-with-homebrew/)
|
||||
27
brew/install-go-packages-in-brewfile.md
Normal file
27
brew/install-go-packages-in-brewfile.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Install Go Packages In Brewfile
|
||||
|
||||
Typically my `Brewfile` is only full of `brew` and `cask` directives. That's
|
||||
starting to change now that `brew` supports installing Go packages listed in the
|
||||
`Brewfile`.
|
||||
|
||||
Use the `go` directive and the URL to the hosted Go package.
|
||||
|
||||
Here is an example of a `Brewfile` that includes a `cask`, `brew`, and `go`
|
||||
directive.
|
||||
|
||||
```
|
||||
# screen resolution tool
|
||||
cask "betterdisplay"
|
||||
|
||||
# Mac keychain management, gpg key
|
||||
brew "pinentry-mac"
|
||||
|
||||
# Sanitized production Postgres dumps
|
||||
go "github.com/jackc/pg_partialcopy"
|
||||
```
|
||||
|
||||
I've recently added the exact package from above to my [`dotfiles`
|
||||
repo](https://github.com/jbranchaud/dotfiles/commit/e83e9d19504f0e2f95eba33123f907f999bf865e).
|
||||
|
||||
Here is the [PR to `brew`](https://github.com/Homebrew/brew/pull/20798) where
|
||||
this functionality was added back in October of 2025.
|
||||
28
brew/list-all-services-managed-by-brew.md
Normal file
28
brew/list-all-services-managed-by-brew.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# List All Services Managed By Brew
|
||||
|
||||
Daemonized services, such as PostgreSQL, can be installed and managed with
|
||||
Homebrew. Under the hood `brew` uses `launchctl` on Mac to manage these
|
||||
services — i.e. starting, restarting, and stopping them.
|
||||
|
||||
Assuming you've already installed some services, you can run `brew services
|
||||
list` to see what services there are and what their current status is.
|
||||
|
||||
```bash
|
||||
$ brew services list
|
||||
Name Status User File
|
||||
mailhog none
|
||||
mysql none
|
||||
postgresql@11 started jbranchaud ~/Library/LaunchAgents/homebrew.mxcl.postgresql@11.plist
|
||||
postgresql@13 none
|
||||
postgresql@16 none
|
||||
unbound none
|
||||
```
|
||||
|
||||
This is the default behavior if you just run `brew services` without a subcommand.
|
||||
|
||||
This is helpful if you are, for instance, trying to see which PostgreSQL server
|
||||
version you are currently running and which other ones are available to run. I
|
||||
might then issue a `stop` to `postgresql@11` so that I can then `start` the
|
||||
`postgresql@16` service.
|
||||
|
||||
See `brew services --help` for more details.
|
||||
14
chrome/open-current-tab-in-new-window-with-vimium.md
Normal file
14
chrome/open-current-tab-in-new-window-with-vimium.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# Open Current Tab In New Window With Vimium
|
||||
|
||||
Sometime I have a busy Chrome window going with a bunch of tabs open for
|
||||
various lines of work as well as a number of tabs that I've neglected to close.
|
||||
I then open a new tab, find something useful, and realize I'm at a "branching
|
||||
point". I'm about to start in on a specific chunk of work that will probably
|
||||
involve opening several more tabs and switch back and forth between some
|
||||
dashboards. I want to start all of this from a fresh slate -- or at least from
|
||||
a fresh Chrome window.
|
||||
|
||||
With [Vimium](https://github.com/philc/vimium), I can hit `W` (`Shift-w`) to
|
||||
have the current tab move from the current window to a new window. The original
|
||||
window, minus that one tab, will be left as is so that I can go back to it as
|
||||
needed.
|
||||
22
chrome/search-tabs-with-the-vimium-vomnibar.md
Normal file
22
chrome/search-tabs-with-the-vimium-vomnibar.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Search Tabs With The Vimium Vomnibar
|
||||
|
||||
If you use Chrome like I do, then you eventually end up with several windows
|
||||
with dozens if not 100+ tabs open. It can start to get tedius with that many
|
||||
tabs to find and navigate to a given tab. Someone might suggest closing a few
|
||||
dozen tabs as a solution to this predicament. However, Vimium offers a solution
|
||||
that doesn't require I [_kill my
|
||||
darlings_](https://en.wiktionary.org/wiki/kill_one%27s_darlings).
|
||||
|
||||
The Vomnibar, a Vimium-powered search bar, can be summoned with `T` to only
|
||||
search through open tabs.
|
||||
|
||||
When I hit `T`, I see a text area (for refining the search) and then a bunch of
|
||||
entries populate below that which I immediately recognize as many of those tabs
|
||||
that I'm going to get back to one of these days.
|
||||
|
||||
To narrow down to the specific thing I'm looking for, I type something into the
|
||||
input. Then I arrow to the result I'm looking for and hit enter. And I'm
|
||||
transported to that tab.
|
||||
|
||||
If I don't like where I ended up, I can also go back to the tab I had been on
|
||||
with `^`.
|
||||
25
chrome/trigger-commands-from-the-devtools-command-palette.md
Normal file
25
chrome/trigger-commands-from-the-devtools-command-palette.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Trigger Commands From The Devtools Command Palette
|
||||
|
||||
There are a ton of tabs, drop downs, and nested menus in Chrome's devtools. If
|
||||
I know what I am looking for, that is great, I can navigate to it without much
|
||||
trouble. But for other features and commands, I'm stumped and can end up
|
||||
spending minutes looking around.
|
||||
|
||||
For example, where is the option to 'Disable JavaScript'?
|
||||
|
||||
I don't know. And I don't need to know.
|
||||
|
||||
Instead of searching around for it, I can pop open the devtools _command
|
||||
palette_ with `cmd+shift+p`. This is a modal menu that prompts me to search for
|
||||
a command to run. I start typing `disab` and already `Disable JavaScript`
|
||||
appears as one of the top options. I can select that option and JavaScript will
|
||||
be disabled.
|
||||
|
||||
When I'm ready to turn it back on. I can hit `cmd+shift+p` again and search for
|
||||
`enab`. The `Enable JavaScript` option appears and I can select it to turn it
|
||||
back on.
|
||||
|
||||
Note: you'll need to have the devtools panel open and your focus will need to
|
||||
be on it for the keybinding to be picked up.
|
||||
|
||||
[source](https://developer.chrome.com/docs/devtools/command-menu)
|
||||
18
claude-code/monitor-usage-limits-from-cli.md
Normal file
18
claude-code/monitor-usage-limits-from-cli.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Monitor Usage Limits From CLI
|
||||
|
||||
When I first started using Claude Code enough to push the usage limits, I would
|
||||
periodically switch over to the browser to check
|
||||
`https://claude.ai/settings/usage` to see how close I was getting. That page
|
||||
would tell me what percentage of my allotted usage I had consumed so far for the
|
||||
current 5-hour session and then how long until that 5-hour usage window resets.
|
||||
|
||||
This can also be viewed directly in Claude Code for the CLI.
|
||||
|
||||
First, run the `/status` slash command and then _tab_ over to the _Usage_
|
||||
section. There you will see the same details as in the web view.
|
||||
|
||||
I'm also learned, as I write this, that you can go directly to the _Usage_
|
||||
section by typing the `/usage` slash command.
|
||||
|
||||
See [the docs](https://code.claude.com/docs/en/slash-commands) for a listing of
|
||||
all slash commands.
|
||||
15
claude-code/open-current-prompt-in-default-editor.md
Normal file
15
claude-code/open-current-prompt-in-default-editor.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Open Current Prompt In Default Editor
|
||||
|
||||
[Claude Code](https://www.claude.com/product/claude-code) gives you a single
|
||||
line to write a prompt. You can write and write as much as you want, but it will
|
||||
all be on that single line. And avoid accidentally hitting 'Enter' before you're
|
||||
done.
|
||||
|
||||
I found myself wanting to space out my thoughts, create a code block as part of
|
||||
a prompt, and generally have a scratch pad instead of just a text box. By
|
||||
hitting `ctrl-g`, I can move the current prompt into my default editor (in my
|
||||
case, `nvim`). From there I can continue to write, edit, and format with all the
|
||||
affordances of an editor.
|
||||
|
||||
Once I'm done crafting the prompt, I can save (e.g. `:wq`) and Claude Code will
|
||||
be primed with that text. I can then hit 'Enter' to let `claude` do its thing.
|
||||
51
css/add-line-numbers-to-a-code-block-with-counter.md
Normal file
51
css/add-line-numbers-to-a-code-block-with-counter.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Add Line Numbers To A Code Block With Counter
|
||||
|
||||
The
|
||||
[`counter`](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_counter_styles/Using_CSS_counters)
|
||||
feature in CSS is a stateful feature that allows you to increment and display a
|
||||
number based on elements' locations in the document. This feature is useful for
|
||||
adding numbers to headings and lists, but it can also be used to add line
|
||||
numbers to a code block.
|
||||
|
||||
We need to initialize the counter to start using it. This will give it a name
|
||||
and default it to the value 0. We'll tie this to a `pre` tag which wraps our
|
||||
lines of code.
|
||||
|
||||
```css {{ title: 'globals.css' }}
|
||||
pre.shiki {
|
||||
counter-reset: line-number;
|
||||
}
|
||||
```
|
||||
|
||||
Then we need to increment the counter for every line of code that appears in
|
||||
the code block
|
||||
|
||||
```css {{ title: 'globals.css' }}
|
||||
pre.shiki .line {
|
||||
counter-increment: line-number;
|
||||
}
|
||||
```
|
||||
|
||||
Last, we need to display these incrementing `line-number` values _before_ each
|
||||
line.
|
||||
|
||||
```css {{ title: 'globals.css }}
|
||||
pre.shiki .line:not(:last-of-type)::before {
|
||||
content: counter(line-number);
|
||||
/*
|
||||
* plus any styling and spacing of the numbers
|
||||
*/
|
||||
}
|
||||
```
|
||||
|
||||
This essentially attaches an element to the front (`::before`) of the line
|
||||
whose content is the current value of `line-number`. It is applied to all but
|
||||
the last `.line` because [shiki](https://shiki.matsu.io/) includes an empty
|
||||
`.line` at the end.
|
||||
|
||||
Here is [the real-world example of
|
||||
this](https://github.com/pingdotgg/uploadthing/blob/4954c9956c141a25a5405991c34cc5ce8d990085/docs/src/styles/tailwind.css#L13-L37)
|
||||
that I referenced for this post.
|
||||
|
||||
Note: the counter can be incremented, decremented, or even explicitly set to a
|
||||
specific value.
|
||||
29
css/filter-blur-requires-expensive-calculation.md
Normal file
29
css/filter-blur-requires-expensive-calculation.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Filter Blur Requires Expensive Calculation
|
||||
|
||||
I had [a
|
||||
page](https://www.visualmode.dev/connect-to-production-rails-console-aws-flightcontrol)
|
||||
on my blog that was experiencing some odd rendering behavior. The issue was
|
||||
manifesting a couple ways.
|
||||
|
||||
- Resizing and scrolling were janky and causing entire page layers to re-render
|
||||
causing the page to flash in and out.
|
||||
- Sometimes entire layer chunks would fail to paint leaving a white block
|
||||
missing from the page.
|
||||
|
||||
The issue was occurring with and without JavaScript turned on for a
|
||||
statically-built page. I suspected that some aspect of the CSS was at fault.
|
||||
|
||||
I was going back and forth with Dillon Hafer about what the issue could be and
|
||||
he wondered, "could it be the backdrop-blur class from tailwind?". I tried
|
||||
removing that class and the responsiveness of the page immediately improved.
|
||||
|
||||
The [`filter:
|
||||
blur`](https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/blur)
|
||||
and [`backdrop-filter:
|
||||
blur`](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter) both
|
||||
use an expensive [Gaussian blur](https://en.wikipedia.org/wiki/Gaussian_blur)
|
||||
calculation. One of these on a modern machine and browser probably won't have a
|
||||
noticable impact. However, a bunch of them, as in the case of my page with a
|
||||
recurring component, can have quite the performance hit.
|
||||
|
||||
[source](https://github.com/tailwindlabs/tailwindcss/issues/15256)
|
||||
29
css/prevent-invisible-elements-from-being-clicked.md
Normal file
29
css/prevent-invisible-elements-from-being-clicked.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Prevent Invisible Elements From Being Clicked
|
||||
|
||||
I have a nav element that when clicked reveals a custom drop-down menu. It
|
||||
reveals it using CSS transitions and transformations (`opacity` and `scale`).
|
||||
When the nav element is clicked again, the reverse of these transformations is
|
||||
applied to "hide" the menu. This gives a nice visual effect.
|
||||
|
||||
It only makes the menu invisible and doesn't actually make it go away. That
|
||||
means that menu could be invisible, but hovering over the top of a button on
|
||||
the screen. The button cannot be clicked now because the menu is intercepting
|
||||
that [_pointer
|
||||
event_](https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events).
|
||||
|
||||
The fix is to apply CSS (or a class) when the drop-down menu is closed that
|
||||
tells it to ignore _pointer events_.
|
||||
|
||||
```css
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
```
|
||||
|
||||
This is more of less what [the `pointer-events-none` TailwindCSS
|
||||
utility](https://tailwindcss.com/docs/pointer-events) looks like.
|
||||
|
||||
This class is applied by default to the drop-down menu. Then when the nav item
|
||||
is clicked, some JavaScript removes that class at the same moment that the menu
|
||||
is visually appearing. When a menu item is selected or the menu otherwise
|
||||
closed, it transitions away and the `pointer-events-none` class is reapplied.
|
||||
32
cursor/allow-cursor-to-be-launched-from-cli.md
Normal file
32
cursor/allow-cursor-to-be-launched-from-cli.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Allow Cursor To Be Launched From CLI
|
||||
|
||||
It is nice to be able to open Cursor for a specific project directly from the
|
||||
terminal like so:
|
||||
|
||||
```bash
|
||||
$ cd ~/dev/my/project
|
||||
|
||||
$ cursor .
|
||||
```
|
||||
|
||||
For the `cursor` launcher binary to be available like that, we have to find it
|
||||
and add it to the path.
|
||||
|
||||
It is probably located in the `/Applications` folder and within that nested down
|
||||
a couple directories is a `bin` directory that contains the binary we're looking
|
||||
for.
|
||||
|
||||
```bash
|
||||
ls /Applications/Cursor.app/Contents/Resources/app/bin
|
||||
bin/
|
||||
├── code*
|
||||
├── cursor*
|
||||
└── cursor-tunnel*
|
||||
```
|
||||
|
||||
The `cursor` binary is what we want, so let's add that to our path. In my case,
|
||||
I'll add this to my `~/.zshrc` file.
|
||||
|
||||
```bash
|
||||
export PATH="/Applications/Cursor.app/Contents/Resources/app/bin:$PATH"
|
||||
```
|
||||
@@ -19,4 +19,4 @@ host `staging` like so:
|
||||
staging ansible_host=192.168.1.50
|
||||
```
|
||||
|
||||
[source](http://docs.ansible.com/ansible/intro_inventory.html)
|
||||
[source](https://docs.ansible.com/ansible/latest/inventory_guide/intro_inventory.html#inventory-aliases)
|
||||
|
||||
28
devops/default-rails-deploy-script-on-hatchbox.md
Normal file
28
devops/default-rails-deploy-script-on-hatchbox.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Default Rails Deploy Script On Hatchbox
|
||||
|
||||
I deployed a Rails app to [Hatchbox](https://hatchbox.io) recently. When
|
||||
following along in the log during a deploy, I can see most of what is happening
|
||||
as part of the deploy. Though it is too verbose to look through every line. I'd
|
||||
rather see the contents of the deploy script.
|
||||
|
||||
I did quite a bit of digging around while SSH'd into my hatchbox server, but I
|
||||
couldn't find if or where that file might be stored.
|
||||
|
||||
Instead, there is a [_Help Center_
|
||||
article](https://hatchbox.relationkit.io/articles/55-what-is-the-default-rails-deploy-script)
|
||||
where Chris Oliver shares what is in the script.
|
||||
|
||||
```bash
|
||||
bundle install -j $(nproc)
|
||||
yarn install
|
||||
bundle exec rails assets:precompile
|
||||
[[ -n "${CRON}" ]] && bundle exec rails db:migrate
|
||||
```
|
||||
|
||||
It does a parallelized `bundle install`, then a `yarn install` (make sure your
|
||||
project is using `yarn.lock`), Rails asset precompilation, and then if `CRON`
|
||||
is set (Cron role is available by checking _Cron_ under _Server
|
||||
Responsibilities_ for your Hatchbox server), it will run Rails migrations.
|
||||
|
||||
From app settings, the deploy script can be overridden, or pre- and post-deploy
|
||||
steps can be added.
|
||||
44
devops/hatchbox-exports-env-vars-with-asdf.md
Normal file
44
devops/hatchbox-exports-env-vars-with-asdf.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Hatchbox Exports Env Vars With asdf
|
||||
|
||||
When you add env vars through the [Hatchbox](https://hatchbox.io/) UI, they get
|
||||
exported to the environment of the asdf-shimmed processes. This is handled by
|
||||
the [`asdf-vars` plugin](https://github.com/excid3/asdf-vars). That plugin
|
||||
looks for `.asdf-vars` in the current chain of directories.
|
||||
|
||||
I can see there are many `.asdf-vars` files:
|
||||
|
||||
```bash
|
||||
$ find . -name ".asdf-vars" -type f
|
||||
./.asdf-vars
|
||||
./my-app/.asdf-vars
|
||||
./my-app/releases/20250120195106/.asdf-vars
|
||||
./my-app/releases/20250121041054/.asdf-vars
|
||||
```
|
||||
|
||||
And it is the one in my app's directory that contains the env vars that I set
|
||||
in the UI.
|
||||
|
||||
```bash
|
||||
$ cat my-app/.asdf-vars
|
||||
BUNDLE_WITHOUT=development:test
|
||||
DATABASE_URL=postgresql://user_123:123456789012345@10.0.1.1/my_app_db
|
||||
PORT=9000
|
||||
RACK_ENV=production
|
||||
RAILS_ENV=production
|
||||
RAILS_LOG_TO_STDOUT=true
|
||||
RAILS_MASTER_KEY=abc123
|
||||
SECRET_KEY_BASE=abc123efg456
|
||||
```
|
||||
|
||||
When I run a shimmed process like `ruby`, those env vars are loaded into the
|
||||
process's environment.
|
||||
|
||||
```bash
|
||||
$ cd my-app/current
|
||||
$ which ruby
|
||||
/home/deploy/.asdf/shims/ruby
|
||||
$ ruby -e "puts ENV['DATABASE_URL']"
|
||||
postgresql://user_123:123456789012345@10.0.1.1/my_app_db
|
||||
```
|
||||
|
||||
[source](https://www.visualmode.dev/hatchbox-manages-env-vars-with-asdf)
|
||||
24
devops/set-up-domain-for-hatchbox-rails-app.md
Normal file
24
devops/set-up-domain-for-hatchbox-rails-app.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Set Up Domain For Hatchbox Rails App
|
||||
|
||||
When we deploy a Rails app with [Hatchbox](https://hatchbox.io), we are given
|
||||
an internal URL for publicly accessing our app. It is something like
|
||||
`https://123abc.hatchboxapp.com`. That's useful as we are getting things up and
|
||||
running, but eventually we want to point our own domain at the app.
|
||||
|
||||
The first step is to tell Hatchbox what domain we are going to use.
|
||||
|
||||
From our app's _Domain & SSL_ page we can enter a domain into the _Add A
|
||||
Domain_ input. For instance, I have the
|
||||
[visualmode.dev](https://visualmode.dev) domain and I want the
|
||||
[still.visualmode.dev](https://still.visualmode.dev) subdomain pointing at my
|
||||
Rails app. I submit the full name `still.visualmode.dev` and I get an _A
|
||||
Record_ ipv4 address (e.g. `23.12.234.82`).
|
||||
|
||||
The second step is to configure a DNS record with our domain registrar.
|
||||
|
||||
From the DNS settings of our registrar (e.g. Cloudflare) we can add an _A
|
||||
Record_ where we specify the name (e.g. `still`) and then include the ipv4
|
||||
address provided by Hatchbox. We can save this and wait a minute for it to
|
||||
propagate.
|
||||
|
||||
And soon enough we can visit our Rails app at the custom domain.
|
||||
28
docker/check-postgres-version-running-in-docker-container.md
Normal file
28
docker/check-postgres-version-running-in-docker-container.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Check Postgres Version Running In Docker Container
|
||||
|
||||
I have a docker container that I'm using to run a PostgreSQL development
|
||||
database on my local machine. It was a while ago when I set it up, so I can't
|
||||
remember specifically which major version of PostgreSQL I am using.
|
||||
|
||||
I use `docker ps` to list the names of each container.
|
||||
|
||||
```bash
|
||||
$ docker ps --format "{{.Names}}"
|
||||
still-postgres-1
|
||||
better_reads-postgres-1
|
||||
```
|
||||
|
||||
I grab the one I am interested in. In this case, that is `still-postgres-1`.
|
||||
|
||||
Then I can execute a `select version()` statement with `psql` against the
|
||||
container with that name like so:
|
||||
|
||||
```bash
|
||||
$ docker exec still-postgres-1 psql -U postgres -c "select version()";
|
||||
version
|
||||
---------------------------------------------------------------------------------------------------------------------
|
||||
PostgreSQL 16.2 (Debian 16.2-1.pgdg120+2) on x86_64-pc-linux-gnu, compiled by gcc (Debian 12.2.0-14) 12.2.0, 64-bit
|
||||
(1 row)
|
||||
```
|
||||
|
||||
And there I have it. I'm running Postgres v16 in this container.
|
||||
34
docker/configure-different-host-and-container-ports.md
Normal file
34
docker/configure-different-host-and-container-ports.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Configure Different Host And Container Ports
|
||||
|
||||
A `docker-compose.yml` file that sets up something like a PostgreSQL service
|
||||
will proxy a port from your host machine to a port on the docker container.
|
||||
|
||||
A basic PostgreSQL service will look like this tying `5432` to `5432` under the
|
||||
`ports` section.
|
||||
|
||||
```yaml
|
||||
version: "3.7"
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- ./postgres-data:/var/lib/postgresql/data
|
||||
```
|
||||
|
||||
Requests like queries from a `psql` instance that we send to `localhost:5432`
|
||||
will be proxied to `docker-container:5432`.
|
||||
|
||||
Since those numbers are the same on both sides, it's not necessarily clear
|
||||
which is which. The left is the _host_ and the right is the _container_ --
|
||||
`[host-port]:[container-port]`.
|
||||
|
||||
If you need to use a port other than 5432 on your host machine (e.g. maybe
|
||||
you're running multiple Postgres servers at once), then you can just change the
|
||||
port number on the left side. How about `9876:5432`.
|
||||
22
docker/list-running-docker-containers.md
Normal file
22
docker/list-running-docker-containers.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# List Running Docker Containers
|
||||
|
||||
The `docker` CLI has a `ps` command that will list all running container by
|
||||
default.
|
||||
|
||||
When I run it, I can see that I have a container running a Postgres database
|
||||
and another running a MySQL database.
|
||||
|
||||
```bash
|
||||
$ docker ps
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
ba792e185734 postgres:latest "docker-entrypoint.s…" 12 days ago Up 12 days 0.0.0.0:9876->5432/tcp better_reads-postgres-1
|
||||
7ca7c1e882e0 mysql:8.0 "docker-entrypoint.s…" 19 months ago Up 8 seconds 33060/tcp, 0.0.0.0:3309->3306/tcp some-app-db-1
|
||||
```
|
||||
|
||||
It lists several pieces of info about the containers: the container id, the
|
||||
image it is based off, when it was created, the running status, the port
|
||||
configuration, and the name of the container
|
||||
|
||||
If I run `docker ps --help` I can see some additional options. One option is
|
||||
the `--all` flag which will display all known docker container instead of just
|
||||
the running ones.
|
||||
53
docker/prevent-containers-from-running-on-startup.md
Normal file
53
docker/prevent-containers-from-running-on-startup.md
Normal file
@@ -0,0 +1,53 @@
|
||||
# Prevent Containers From Running On Startup
|
||||
|
||||
I have a bunch of docker containers managed by Docker Desktop. Some are related
|
||||
to projects I'm actively working on. Whereas many others are inactive projects.
|
||||
|
||||
When I restart my machine, regardless of which containers I had running or
|
||||
turned off, several of them are booted into a running state on startup. This is
|
||||
becaue their restart policy is set to `always`. That's fine for the project I'm
|
||||
actively working on, but the others I would like to be _off_ by default.
|
||||
|
||||
I need to update each of their restart policies from `always` to `no`.
|
||||
|
||||
First, I need to figure out their container IDs:
|
||||
|
||||
```bash
|
||||
$ docker ps --all
|
||||
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
||||
eb7b40aeba2d postgres:latest "docker-entrypoint.s…" 3 months ago Up 11 minutes 0.0.0.0:9875->5432/tcp still-postgres-1
|
||||
eb9ab2213f2b postgres:latest "docker-entrypoint.s…" 3 months ago Exited (0) 11 minutes ago next-drizzle-migration-repro-app-postgres-1
|
||||
ba792e185734 postgres:latest "docker-entrypoint.s…" 4 months ago Up 11 minutes 0.0.0.0:9876->5432/tcp better_reads-postgres-1
|
||||
3139f9beae76 postgres:latest "docker-entrypoint.s…" 9 months ago Exited (128) 7 months ago basic-next-prisma-postgres-1
|
||||
```
|
||||
|
||||
Referencing the `CONTAINER ID` and `NAMES` columns, I'm able to then inspect
|
||||
each container and see the current `RestartPolicy`:
|
||||
|
||||
```bash
|
||||
$ docker inspect eb9ab2213f2b | grep -A3 RestartPolicy
|
||||
"RestartPolicy": {
|
||||
"Name": "always",
|
||||
"MaximumRetryCount": 0
|
||||
},
|
||||
```
|
||||
|
||||
I can then update the `RestartPolicy` to be `no`:
|
||||
|
||||
```bash
|
||||
$ docker update --restart no eb9ab2213f2b
|
||||
```
|
||||
|
||||
Inpsecting that container again, I can see the updated policy:
|
||||
|
||||
```bash
|
||||
$ docker inspect eb9ab2213f2b | grep -A3 RestartPolicy
|
||||
"RestartPolicy": {
|
||||
"Name": "no",
|
||||
"MaximumRetryCount": 0
|
||||
},
|
||||
```
|
||||
|
||||
Rinse and repeat for each of the offending containers.
|
||||
|
||||
[source](https://stackoverflow.com/questions/45423334/stopping-docker-containers-from-being-there-on-startup)
|
||||
36
docker/run-a-basic-postgresql-server-in-docker.md
Normal file
36
docker/run-a-basic-postgresql-server-in-docker.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Run A Basic PostgreSQL Server In Docker
|
||||
|
||||
Here is a basic `docker-compose.yml` file for spinning up a Docker container
|
||||
that runs a PostgreSQL server on port 5432. This is what I use to create a
|
||||
locally-running PostgreSQL server that lives inside a docker container.
|
||||
|
||||
```yaml
|
||||
version: "3.7"
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:latest
|
||||
restart: always
|
||||
environment:
|
||||
- POSTGRES_USER=postgres
|
||||
- POSTGRES_PASSWORD=postgres
|
||||
- POSTGRES_DB=postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
volumes:
|
||||
- ./postgres-data:/var/lib/postgresql/data
|
||||
```
|
||||
|
||||
To create the docker container and start it up, run the following command from
|
||||
the same directory where you put this file:
|
||||
|
||||
```bash
|
||||
$ docker compose up
|
||||
```
|
||||
|
||||
This command knows to look for the `docker-compose.yml` file though you can
|
||||
always be explicit about the file with the `-f` option.
|
||||
|
||||
This configuration points at `postgres:latest` which currently is `16.1`. To
|
||||
run a different major version, you can change the `image` to something like
|
||||
`postgres:15`. See [Docker Hub](https://hub.docker.com/_/postgres) for more
|
||||
options.
|
||||
42
docker/run-sql-script-against-postgres-container.md
Normal file
42
docker/run-sql-script-against-postgres-container.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Run SQL Script Against Postgres Container
|
||||
|
||||
I've been using dockerized Postgres for local development with several projects
|
||||
lately. This is typically with framework tooling (like Rails) where schema
|
||||
migrations and query execution are handled by the tooling using the specified
|
||||
connection parameters.
|
||||
|
||||
However, I was experimenting with and iterating on some Postgres functions
|
||||
outside of any framework tooling. I needed a way to run the SQL script that
|
||||
(re)creates the function via `psql` on the docker container.
|
||||
|
||||
With a local, non-containerized Postgres instance, I'd redirect the file to
|
||||
`psql` like so:
|
||||
|
||||
```bash
|
||||
$ psql -U postgres -d postgres < experimental-functions.sql
|
||||
```
|
||||
|
||||
When I tried doing this with `docker exec` though, it was silently failing /
|
||||
doing nothing. As far as I can tell, there was a mismatch with redirection
|
||||
handling across the bounds of the container.
|
||||
|
||||
To get around this, I first copy the file into the `/tmp` directory on the
|
||||
container:
|
||||
|
||||
```bash
|
||||
$ docker cp experimental-functions.sql still-postgres-1:/tmp/experimental-functions.sql
|
||||
```
|
||||
|
||||
Then the `psql` command that docker executes can be pointed directly at a
|
||||
local-to-it SQL file.
|
||||
|
||||
```bash
|
||||
$ docker exec still-postgres-1 psql \
|
||||
-U postgres \
|
||||
-d postgres \
|
||||
-f /tmp/experimental-functions.sql
|
||||
```
|
||||
|
||||
There are probably other ways to handle this, but I got into a nice rhythm with
|
||||
this file full of `create or replace function ...` definitions where I could
|
||||
modify, copy over, execute, run some SQL to verify, and repeat.
|
||||
4
dprint.json
Normal file
4
dprint.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"excludes": ["README.md"],
|
||||
"plugins": ["https://plugins.dprint.dev/markdown-0.16.0.wasm"]
|
||||
}
|
||||
48
drizzle/create-bigint-identity-column-for-primary-key.md
Normal file
48
drizzle/create-bigint-identity-column-for-primary-key.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Create bigint Identity Column For Primary Key
|
||||
|
||||
Using the Drizzle ORM with Postgres, here is how we can create a table that
|
||||
uses a [`bigint` data
|
||||
type](https://orm.drizzle.team/docs/column-types/pg#bigint) as a primary key
|
||||
[identity
|
||||
column](https://www.postgresql.org/docs/current/ddl-identity-columns.html).
|
||||
|
||||
```typescript
|
||||
import {
|
||||
pgTable,
|
||||
bigint,
|
||||
text,
|
||||
timestamp,
|
||||
} from "drizzle-orm/pg-core";
|
||||
|
||||
// Users table
|
||||
export const users = pgTable("users", {
|
||||
id: bigint({ mode: 'bigint' }).primaryKey().generatedAlwaysAsIdentity(),
|
||||
email: text("email").unique().notNull(),
|
||||
name: text("name").notNull(),
|
||||
createdAt: timestamp("created_at").defaultNow().notNull(),
|
||||
});
|
||||
```
|
||||
|
||||
There are a couple key pieces here:
|
||||
|
||||
1. We import `bigint` so that we can declare a column of that type.
|
||||
2. We specify that it is a primary key with `.primaryKey()`.
|
||||
3. We declare its default value as `generated always as identity` via
|
||||
`.generatedAlwaysAsIdentity()`.
|
||||
|
||||
Note: you need to specify the `mode` for `bigint` or else you will see a
|
||||
`TypeError: Cannot read properties of undefined (reading 'mode')` error.
|
||||
|
||||
If we run `npx drizzle-kit generate` the SQL migration file that gets
|
||||
generated will contain something like this:
|
||||
|
||||
```sql
|
||||
--> statement-breakpoint
|
||||
CREATE TABLE IF NOT EXISTS "users" (
|
||||
"id" bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),
|
||||
"email" text NOT NULL,
|
||||
"name" text NOT NULL,
|
||||
"created_at" timestamp DEFAULT now() NOT NULL,
|
||||
CONSTRAINT "users_email_unique" UNIQUE("email")
|
||||
);
|
||||
```
|
||||
39
drizzle/drizzle-tracks-migrations-in-a-log-table.md
Normal file
39
drizzle/drizzle-tracks-migrations-in-a-log-table.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Drizzle Tracks Migrations In A Log Table
|
||||
|
||||
When I generate (`npx drizzle-kit generate`) and apply (`npx drizzle-kit
|
||||
migrate`) schema migrations against my database with Drizzle, there are SQL
|
||||
files that get created and run.
|
||||
|
||||
How does Drizzle know which SQL files have been run and which haven't?
|
||||
|
||||
Like many SQL schema migration tools, it uses a table in the database to record
|
||||
this metadata. Drizzle defaults to calling this table `__drizzle_migrations`
|
||||
and puts it in the `drizzle` schema (which is like a database namespace).
|
||||
|
||||
Let's take a look at this table for a project with two migrations:
|
||||
|
||||
```sql
|
||||
postgres> \d drizzle.__drizzle_migrations
|
||||
Table "drizzle.__drizzle_migrations"
|
||||
Column | Type | Collation | Nullable | Default
|
||||
------------+---------+-----------+----------+----------------------------------------------------------
|
||||
id | integer | | not null | nextval('drizzle.__drizzle_migrations_id_seq'::regclass)
|
||||
hash | text | | not null |
|
||||
created_at | bigint | | |
|
||||
Indexes:
|
||||
"__drizzle_migrations_pkey" PRIMARY KEY, btree (id)
|
||||
|
||||
postgres> select * from drizzle.__drizzle_migrations;
|
||||
id | hash | created_at
|
||||
----+------------------------------------------------------------------+---------------
|
||||
1 | 8961353bf66f9b3fe1a715f6ea9d9ef2bc65697bb8a5c2569df939a61e72a318 | 1730219291288
|
||||
2 | b75e61451e2ce37d831608b1bc9231bf3af09e0ab54bf169be117de9d4ff6805 | 1730224013018
|
||||
(2 rows)
|
||||
```
|
||||
|
||||
Notice that Drizzle stores each migration record as [a SHA256 hash of the
|
||||
migration
|
||||
file](https://github.com/drizzle-team/drizzle-orm/blob/526996bd2ea20d5b1a0d65e743b47e23329d441c/drizzle-orm/src/migrator.ts#L52)
|
||||
and a timestamp of when the migration was run.
|
||||
|
||||
[source](https://orm.drizzle.team/docs/drizzle-kit-migrate#applied-migrations-log-in-the-database)
|
||||
56
drizzle/get-fields-for-inserted-row.md
Normal file
56
drizzle/get-fields-for-inserted-row.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Get Fields For Inserted Row
|
||||
|
||||
With Drizzle, we can insert a row with a set of values like so:
|
||||
|
||||
```typescript
|
||||
await db
|
||||
.insert(todoItems)
|
||||
.values({
|
||||
title,
|
||||
userId,
|
||||
description,
|
||||
})
|
||||
```
|
||||
|
||||
The result of this is `QueryResult<never>`. In other words, nothing useful is
|
||||
coming back to us from the database.
|
||||
|
||||
Sometimes an insert is treated as a fire-and-forget (as long as it succeeds) or
|
||||
since we know what data we are inserting, we don't need the database to
|
||||
response. But what about values that are generated or computed by the database
|
||||
-- such as an id from a sequence, timestamp columns that default to `now()`, or
|
||||
generated columns.
|
||||
|
||||
To get all the fields of a freshly inserted row, we can tack on [the
|
||||
`returning()` function](https://orm.drizzle.team/docs/insert#insert-returning)
|
||||
(which likely adds something like [`returning
|
||||
*`](https://www.postgresql.org/docs/current/dml-returning.html)) to the insert
|
||||
query under the hood).
|
||||
|
||||
```typescript
|
||||
await db
|
||||
.insert(todoItems)
|
||||
.values({
|
||||
title,
|
||||
userId,
|
||||
description,
|
||||
})
|
||||
.returning()
|
||||
```
|
||||
|
||||
This will have a return type of `Array<type todoItems>` which means that for
|
||||
each inserted row we'll have all the fields (columns) for that row.
|
||||
|
||||
Alternatively, if we just need the generated ID for the new row(s), we can use
|
||||
a partial return like so:
|
||||
|
||||
```typescript
|
||||
await db
|
||||
.insert(todoItems)
|
||||
.values({
|
||||
title,
|
||||
userId,
|
||||
description,
|
||||
})
|
||||
.returning({ id: todoItems.id })
|
||||
```
|
||||
26
git/add-only-tracked-files-from-a-directory.md
Normal file
26
git/add-only-tracked-files-from-a-directory.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Add Only Tracked Files From A Directory
|
||||
|
||||
The two extremes of staging files in a git repo are to either selectively pick
|
||||
each individual chunk of changes with `git add --patch` (my preference!) or to
|
||||
run `git add -A` to add everything.
|
||||
|
||||
Now let's say I have large directory full of files that get generated during
|
||||
test runs. Most of these files are tracked (already checked in to the
|
||||
repository). There are also many new files generated as part of the most recent
|
||||
test run.
|
||||
|
||||
I want to stage the changes to files that are already tracked, but hold off on
|
||||
doing anything with the new files.
|
||||
|
||||
Running `git add spec/cassettes` won't do the trick because that will pull in
|
||||
everything. Running `git add --patch spec/cassettes` will take long and be
|
||||
tedious. Instead what I want is the `-u` flag. It's short for _update_ which
|
||||
means it will only stage already tracked files.
|
||||
|
||||
```bash
|
||||
$ git add -u spec/cassettes
|
||||
```
|
||||
|
||||
That will stage every change to any already known files in `spec/cassettes`.
|
||||
|
||||
See `man git-add` for more 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.
|
||||
28
git/check-how-a-file-is-being-ignored.md
Normal file
28
git/check-how-a-file-is-being-ignored.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Check How A File Is Being Ignored
|
||||
|
||||
There are a few places on your machine where you can specify the files that git
|
||||
should ignore. The most common is a repository's `.gitignore` file. The other
|
||||
places those excludes are specified can be more obscure. Fortunately, `git
|
||||
check-ignore` is a command that can show you specifically where.
|
||||
|
||||
For instance, let's check why my `notes.md` file is being ignored.
|
||||
|
||||
```bash
|
||||
$ git check-ignore -v .DS_Store
|
||||
.git/info/exclude:7:notes.md notes.md
|
||||
```
|
||||
|
||||
At some point I added it to my repo's `.git/info/exclude` file. The `-v` flag
|
||||
(_verbose_) when included with `check-ignore` tells me the file location.
|
||||
|
||||
How about these pesky `.DS_Store` directories? How are those being ignored?
|
||||
|
||||
```bash
|
||||
$ git check-ignore -v .DS_Store
|
||||
/Users/jbranchaud/.gitignore:3:.DS_Store .DS_Store
|
||||
```
|
||||
|
||||
Ah yes, I had added it to my _global exclude file_ which I've configured in
|
||||
`~/.gitconfig` to be the `~/.gitignore` file.
|
||||
|
||||
See `man git-check-ignore` for more details.
|
||||
38
git/check-if-a-file-has-changed-in-a-script.md
Normal file
38
git/check-if-a-file-has-changed-in-a-script.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Check If A File Has Changed In A Script
|
||||
|
||||
If I'm at the command line and I want to check if a file has changed, I can run
|
||||
`git diff` and see what has changed. If I want to be more specific, I can run
|
||||
`git diff README.md` to see if there are changes to that specific file.
|
||||
|
||||
If I'm trying to do this check in a script though, I want the command to clearly
|
||||
tell the script _Yes_ or _No_. Usually a script looks for an exit code to
|
||||
determine what path to take. But as long as `git diff` runs successfully,
|
||||
regardless of whether or not their are changes, it is going to have an
|
||||
affirmative exit code of `0`.
|
||||
|
||||
This is why `git diff` offers the `--exit-code` flag.
|
||||
|
||||
> Make the program exit with codes similar to diff(1). That is, it exits with 1
|
||||
> if there were differences and 0 means no differences.
|
||||
|
||||
With that in mind, we can wire up a script with `git diff` that takes different
|
||||
paths depending on whether or not there are changes.
|
||||
|
||||
```bash
|
||||
if ! git diff --exit-code README.md; then
|
||||
echo "README.md has changes"
|
||||
else
|
||||
echo "README.md is clean"
|
||||
fi
|
||||
```
|
||||
|
||||
We can take this a step further and instead use the `--quiet` flag.
|
||||
|
||||
> Disable all output of the program. Implies --exit-code. Disables execution of
|
||||
> external diff helpers whose exit code is not trusted
|
||||
|
||||
This exhibits the same behavior as `--exit-code` and goes the additional step of
|
||||
silencing diff output and disabling execution of external diff helpers like
|
||||
`delta`.
|
||||
|
||||
See `man git-diff` for more details.
|
||||
36
git/check-if-a-file-is-under-version-control.md
Normal file
36
git/check-if-a-file-is-under-version-control.md
Normal file
@@ -0,0 +1,36 @@
|
||||
# Check If A File Is Under Version Control
|
||||
|
||||
The `git ls-files` command can be used with the `--error-unmatch` flag to check
|
||||
if a file is under version control. It does this by checking if any of the
|
||||
listed files appears on the _index_. If any does not, it is treated as an error.
|
||||
|
||||
In a project, I have a `README.md` that is under version control. And I have
|
||||
`node_modules` that shouldn't be under version control (which is why they are
|
||||
listed in my `.gitignore` file). I can check the README and a file somewhere in
|
||||
`node_modules`.
|
||||
|
||||
```bash
|
||||
❯ git ls-files --error-unmatch README.md
|
||||
README.md
|
||||
|
||||
❯ git ls-files --error-unmatch node_modules/@ai-sdk/anthropic/CHANGELOG.md
|
||||
error: pathspec 'node_modules/@ai-sdk/anthropic/CHANGELOG.md' did not match any file(s) known to git
|
||||
Did you forget to 'git add'?
|
||||
```
|
||||
|
||||
Notice the second command results in an error because of the untracked
|
||||
`CHANGELOG.md` file in `node_modules`.
|
||||
|
||||
Here is another example of this at work while specifying multiple files:
|
||||
|
||||
```bash
|
||||
❯ git ls-files --error-unmatch README.md node_modules/@ai-sdk/anthropic/CHANGELOG.md package.json
|
||||
README.md
|
||||
package.json
|
||||
error: pathspec 'node_modules/@ai-sdk/anthropic/CHANGELOG.md' did not match any file(s) known to git
|
||||
Did you forget to 'git add'?
|
||||
```
|
||||
|
||||
Each tracked file gets listed and then the untracked file results in an error.
|
||||
|
||||
See `man git-ls-files` for more details.
|
||||
35
git/cherry-pick-multiple-commits-at-once.md
Normal file
35
git/cherry-pick-multiple-commits-at-once.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Cherry Pick Multiple Commits At Once
|
||||
|
||||
I've always thought of `git cherry-pick` as being a command that you can run
|
||||
against a single commit by specifying the SHA of that commit. That's how I've
|
||||
always used it.
|
||||
|
||||
The man page for `git-cherry-pick` plainly states:
|
||||
|
||||
> Given one or more existing commits, apply the change each one introduces,
|
||||
> recording a new commit for each.
|
||||
|
||||
We can cherry pick multiple commits at once in a single command. They will be
|
||||
applied one at a time in the order listed.
|
||||
|
||||
Here we can see an example of applying two commits to the current branch and
|
||||
the accompanying output as they are auto-merged.
|
||||
|
||||
```bash
|
||||
$ git cherry-pick 5206af5 6362f41
|
||||
Auto-merging test/services/event_test.rb
|
||||
[jb/my-feature-branch 961f3deb] Use the other testing syntax
|
||||
Date: Fri May 2 10:50:14 2025 -0500
|
||||
1 file changed, 7 insertions(+), 7 deletions(-)
|
||||
Auto-merging test/services/event_test.rb
|
||||
[jb/my-feature-branch b15835d0] Make other changes to the test
|
||||
Date: Fri May 2 10:54:48 2025 -0500
|
||||
1 file changed, 7 insertions(+), 7 deletions(-)
|
||||
```
|
||||
|
||||
If the commits cannot be cleanly merged, then you may need to do some manual
|
||||
resolution as they are applied. Or maybe you want to try including the
|
||||
`-Xpatience` merge strategy.
|
||||
|
||||
See `man git-cherry-pick` for more details. Make sure to look at the _Examples_
|
||||
section which contains much more advanced examples beyond what is shown above.
|
||||
26
git/clear-entries-from-git-stash.md
Normal file
26
git/clear-entries-from-git-stash.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Clear Entries From Git Stash
|
||||
|
||||
I often stash changes as I'm moving between branches, rebasing, or pulling in
|
||||
changes from the remote. Usually these are changes that I will want to restore
|
||||
with a `git stash pop` in a few moments.
|
||||
|
||||
However, sometimes these stashed changes are abandoned to time.
|
||||
|
||||
When I run `git stash list` on an active project, I see that there are nine
|
||||
entries in the list. When I do `git show stash@{0}` and `git show stash@{1}` to
|
||||
see the changes that comprise the latest two entries, I don't see anything I
|
||||
care about.
|
||||
|
||||
I can get rid of those individual entries with, say, `git stash drop
|
||||
stash@{0}`.
|
||||
|
||||
But I'm pretty confident that I don't care about any of the nine entries in my
|
||||
stash list, so I want to _clear_ out all of them. I can do that with:
|
||||
|
||||
```bash
|
||||
$ git stash clear
|
||||
```
|
||||
|
||||
Now when I run `git stash list`, I see nothing.
|
||||
|
||||
See `man git-stash` for more details.
|
||||
27
git/count-all-files-of-specific-type-tracked-by-git.md
Normal file
27
git/count-all-files-of-specific-type-tracked-by-git.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Count All Files Of Specific Type Tracked By Git
|
||||
|
||||
I want to get a count of all the markdown files in my [TIL
|
||||
repo](https://github.com/jbranchaud/til). Since all the files I care about are
|
||||
tracked by `git`, I can use `git ls-files` to get a listing of all files. That
|
||||
command on its own lists all files tracked by your git repository. Though there
|
||||
are many other flags we can apply, that will do for my purposes.
|
||||
|
||||
By giving `git ls-files` a pattern to match against, I can turn up just, for
|
||||
instance, markdown files (`*.md`). I can pipe that to `wc -l` to get a count
|
||||
rather than exploding my terminal with a list of file names.
|
||||
|
||||
```bash
|
||||
❯ git ls-files '*.md' | wc -l
|
||||
1503
|
||||
```
|
||||
|
||||
That command includes `README.md` and `CONTRIBUTING.md`, but really I only want
|
||||
to count the markdown files that constitute a TIL. Those all happen to be
|
||||
nested under a single directory. So I can tweak the glob pattern like so:
|
||||
|
||||
```bash
|
||||
❯ git ls-files '*/*.md' | wc -l
|
||||
1501
|
||||
```
|
||||
|
||||
See `man git-ls-files` for more details.
|
||||
48
git/count-number-of-commits-on-a-branch.md
Normal file
48
git/count-number-of-commits-on-a-branch.md
Normal file
@@ -0,0 +1,48 @@
|
||||
# Count Number Of Commits On A Branch
|
||||
|
||||
The `git rev-list` command will show all commits that fit the given revision
|
||||
criteria. By adding in the `--count` flag, we get a count of the number of
|
||||
commits that would have been displayed. Knowing this, we can get the count of
|
||||
commits for the current branch like so:
|
||||
|
||||
```bash
|
||||
$ git rev-list --count HEAD
|
||||
4
|
||||
```
|
||||
|
||||
This finds and counts commits from `HEAD` (usually the top of the current
|
||||
branch) all the back in reverse chronological order to the beginning of the
|
||||
branch (typically the beginning of the repository). This works exactly as
|
||||
expected for a the `main` branch.
|
||||
|
||||
What about when we are on a feature branch though?
|
||||
|
||||
Let's say we've branched off `main` and made a few commits. And now we want the
|
||||
count.
|
||||
|
||||
```bash
|
||||
$ git rev-list --count HEAD
|
||||
7
|
||||
```
|
||||
|
||||
Unfortunately, that is counting up the commits on the feature branch but it
|
||||
keeps counting all the way back to the beginning of the repo.
|
||||
|
||||
If we want a count of just the commits on the current branch, then we can
|
||||
specify a range: from whatever `main` was when we branched to the `HEAD` of
|
||||
this branch.
|
||||
|
||||
```bash
|
||||
$ git rev-list --count HEAD
|
||||
3
|
||||
```
|
||||
|
||||
This is the same as saying, I want all commits on `HEAD`, but exclude (`^`) the
|
||||
commits on `main`:
|
||||
|
||||
```bash
|
||||
git rev-list --count HEAD ^main
|
||||
3
|
||||
```
|
||||
|
||||
See `man git-rev-list` for more details.
|
||||
32
git/exclude-a-directory-during-a-command.md
Normal file
32
git/exclude-a-directory-during-a-command.md
Normal file
@@ -0,0 +1,32 @@
|
||||
# Exclude A Directory During A Command
|
||||
|
||||
Many of the git commands we use, such as `git add`, `git restore`, etc., target
|
||||
files and paths relative to the current directory. This is typically exactly
|
||||
what we want, to stage and unstage and so forth the files and directories in
|
||||
front of us.
|
||||
|
||||
I recently ran into a situation where I needed to restore a small subset of
|
||||
changes. At the same time, I had a massive number of auto-generated files
|
||||
recording HTTP interactions (hundreds of files, modified on the working tree).
|
||||
I wanted to run a `git restore`, but wading through all those HTTP recording
|
||||
files was not feasible.
|
||||
|
||||
I needed to exclude those files. They all belonged to a `spec/cassettes`
|
||||
directory. I could exclude them with a _pathspec_ magic signature pattern which
|
||||
is used to alter and limit the paths in a git command.
|
||||
|
||||
A _pathspec_ magic signature is a special pattern made up of a `:` followed by
|
||||
some signature declaring what the pattern means.
|
||||
|
||||
The `(exclude)`, `!`, and `^` magic signatures all mean the same thing —
|
||||
exclude. So, we can exclude a directory from a `git restore` command like so:
|
||||
|
||||
```bash
|
||||
$ git restore --patch -- . ':!spec/cassettes'
|
||||
```
|
||||
|
||||
We've employed two pathspec patterns here. The first, `.`, scopes everything to
|
||||
the current directory. The second, `':!spec/cassettes'` excludes everything in
|
||||
the `spec/cassettes` directory.
|
||||
|
||||
See `man gitglossary` for more on _pathspecs_.
|
||||
26
git/extend-git-with-custom-commands.md
Normal file
26
git/extend-git-with-custom-commands.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Extend Git With Custom Commands
|
||||
|
||||
I recently learned about the [`git
|
||||
absorb`](https://github.com/tummychow/git-absorb) command. It is interesting in
|
||||
its own right, but I bring it up because it isn't a command that is built in to
|
||||
git. When I was looking at the installation instructions for it, it didn't say
|
||||
anything about how to _register_ the command with `git`.
|
||||
|
||||
How is git supposed to know about it? How do you extend git with custom
|
||||
commands?
|
||||
|
||||
What I learned exploring those questions is that `git` will execute any command
|
||||
on your _path_ with a `git-<command>` naming convention.
|
||||
|
||||
So, if I create a executable binary called `git-taco`, add it to my path, and
|
||||
then run `git taco` (notice, no dash when I run it), `git` will run my custom
|
||||
binary.
|
||||
|
||||
In the same way, if you download `git-absorb` and add it to your path, `git`
|
||||
will run it for you when you enter `git absorb ...`.
|
||||
|
||||
You can even type something like `where git-` and then hit tab to prompt your
|
||||
shell to display a list of a varity of other `git` commands, most of which
|
||||
probably ship with `git`.
|
||||
|
||||
[source](https://twitter.com/jbrancha/status/1756361704160530555)
|
||||
26
git/files-with-local-changes-cannot-be-removed.md
Normal file
26
git/files-with-local-changes-cannot-be-removed.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Files With Local Changes Cannot Be Removed
|
||||
|
||||
This is a nice quality-of-life feature in `git` that should help you avoid
|
||||
accidentally discarding changes that won't be retrievable.
|
||||
|
||||
```bash
|
||||
❯ git rm .tool-versions
|
||||
error: the following file has local modifications:
|
||||
.tool-versions
|
||||
(use --cached to keep the file, or -f to force removal)
|
||||
```
|
||||
|
||||
My `.tool-versions` file has some local changes. I don't realize that and I go
|
||||
to issue a `git rm` command on that file. Instead of quietly wiping out my
|
||||
changes, `git` lets me know I'm doing something destructive (these local
|
||||
changes won't be in the diff or the reflog).
|
||||
|
||||
I can force the removal if I know what I'm doing with the `-f` flag. Or I can
|
||||
take the two step approach of calling `git restore` on that file and then `git
|
||||
rm`.
|
||||
|
||||
The `--cached` flag is also interesting because it doesn't actually delete the
|
||||
file from my file system, but it does stage the file deletion with `git`. That
|
||||
means the file now shows up as one of my untracked files.
|
||||
|
||||
See `man git-rm` for more details.
|
||||
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)
|
||||
25
git/get-latest-commit-timestamp-for-a-file.md
Normal file
25
git/get-latest-commit-timestamp-for-a-file.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Get Latest Commit Timestamp For A File
|
||||
|
||||
The `git log` command can tell you all the commits that touched a file. That
|
||||
can be narrowed down to the latest commit for that file with the `-1` flag. The
|
||||
commit that it reports can then be further formatted to with the `--format`
|
||||
flag.
|
||||
|
||||
The `%ai` format pattern gives the date the commit was authored in an ISO
|
||||
8601-like format. The `%aI` (capital `I`) gives the date the commit was
|
||||
authored strictly in the ISO 8601 format.
|
||||
|
||||
Here are examples of both side by side:
|
||||
|
||||
```bash
|
||||
❯ git log -1 --format=%ai -- README.md
|
||||
2024-10-15 13:59:09 -0500
|
||||
|
||||
❯ git log -1 --format=%aI -- README.md
|
||||
2024-10-15T13:59:09-05:00
|
||||
```
|
||||
|
||||
I made use of this in a script where I needed to get an idea of when various
|
||||
files were most recently modified.
|
||||
|
||||
See `man git-log` and the `PRETTY FORMATS` section for more details.
|
||||
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.
|
||||
44
git/highlight-small-change-on-single-line.md
Normal file
44
git/highlight-small-change-on-single-line.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# Highlight Small Change On Single Line
|
||||
|
||||
Sometimes a change gets made on a single, long line of text in a Git tracked
|
||||
file. If it is a small, subtle change, then it can be hard to pick out when
|
||||
looking at the diff. A standard diff will show a green line of text stacked on
|
||||
a red line of text with no more granular information.
|
||||
|
||||
There are two ways we can improve the diff output in these situations.
|
||||
|
||||
The first is built-in to git. It is the `--word-diff` flag which will visually
|
||||
isolate the portions of the line that have changed.
|
||||
|
||||
```bash
|
||||
git diff --word-diff README.md
|
||||
```
|
||||
|
||||
Which will produce something like this:
|
||||
|
||||
```diff
|
||||
A collection of concise write-ups on small things I learn [-day to day-]{+day-to-day+} across a
|
||||
```
|
||||
|
||||
The outgoing part is wrapped in `[-...-]` and the incoming part is wrapped in
|
||||
`{+...+}`.
|
||||
|
||||
The second (and my preference) is to use
|
||||
[`delta`](https://github.com/dandavison/delta) as an external differ and pager
|
||||
for git.
|
||||
|
||||
```bash
|
||||
git -c core.pager=delta diff README.md
|
||||
```
|
||||
|
||||
I cannot visually demonstrate the difference in a standard code block. So I'll
|
||||
describe it. We see a red and green line stacked on each other, but with muted
|
||||
background colors. Then the specific characters that are different stand out
|
||||
because they are highlighted with brighter red and green. I [posted a visual
|
||||
here](https://bsky.app/profile/jbranchaud.bsky.social/post/3ln245orlxs2j).
|
||||
|
||||
`delta` can also be added as a standard part of your config like I demonstrate
|
||||
in [Better Diffs With Delta](git/better-diffs-with-delta.md).
|
||||
|
||||
h/t to [Dillon Hafer's post on
|
||||
`--word-diff`](https://til.hashrocket.com/posts/t994rwt3fg-finds-diffs-in-long-line)
|
||||
18
git/interactively-checkout-specific-files-from-a-stash.md
Normal file
18
git/interactively-checkout-specific-files-from-a-stash.md
Normal file
@@ -0,0 +1,18 @@
|
||||
# Interactively Checkout Specific Files From A Stash
|
||||
|
||||
This command will prompt you with a list of the stashes in your current git
|
||||
repo. Once you select one of them from the interactive fzf prompt, you will
|
||||
then be prompted with another fzf prompt, this second one allow multi-select.
|
||||
From there you can tab select the files you want to checkout. Once you've
|
||||
marked the ones you want, hit enter and those files will be checked out to your
|
||||
index.
|
||||
|
||||
```bash
|
||||
git stash show --name-only $(
|
||||
git stash list \
|
||||
| fzf --height=20% --reverse \
|
||||
| sed 's/:.*//'
|
||||
) \
|
||||
| fzf --height=20% --multi --sync \
|
||||
| xargs -I {} sh -c 'git checkout stash@{0} -- {}'
|
||||
```
|
||||
20
git/keep-file-locally-with-git-rm.md
Normal file
20
git/keep-file-locally-with-git-rm.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Keep File Locally With `git rm`
|
||||
|
||||
Let's say I've added a new file `data.json` to my repo as part of the most
|
||||
recent commit. I realize this isn't the point at which I want to add that file.
|
||||
So, I do `git rm data.json` and then `git commit --amend` to rework that
|
||||
commit.
|
||||
|
||||
However, when I look in my working tree, or even just my file system, I'll
|
||||
notice that `data.json` is gone. The `git rm` command completely removed the
|
||||
file since it was previously an untracked file.
|
||||
|
||||
To keep `git rm` from tossing out my file like that, I can include the
|
||||
`--cached` flag which will remove the file from the index (stages it to be
|
||||
`deleted`), but restore it to the working directory.
|
||||
|
||||
```bash
|
||||
$ git rm --cached data.json
|
||||
```
|
||||
|
||||
See `man git-rm` for more details on the `--cached` flag.
|
||||
49
git/list-all-files-added-during-span-of-time.md
Normal file
49
git/list-all-files-added-during-span-of-time.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# List All Files Added During Span Of Time
|
||||
|
||||
I wanted to get an idea of all the TIL posts I wrote during 2024. Every TIL I
|
||||
write is under version control in a [git repo on
|
||||
github](https://github.com/jbranchaud/til). That means git has all the info I
|
||||
need to figure that out.
|
||||
|
||||
The `git diff` command is a good way at this problem. With the
|
||||
`--diff-filter=A` flag I can restrict the results to just files that were
|
||||
_Added_. And with `--name-only` I can cut all the other diff details out and
|
||||
get just filenames.
|
||||
|
||||
But filenames added to which commits? We need to specify a ref range. There is
|
||||
a ton of flexibility in how you define a ref, including [a date specification
|
||||
suffix](https://git-scm.com/docs/gitrevisions#Documentation/gitrevisions.txt-emltrefnamegtltdategtemegemmasteryesterdayememHEAD5minutesagoem)
|
||||
that points to the value of the ref at an earlier point in time.
|
||||
|
||||
So, how about from the beginning of 2024 to the beginning of 2025:
|
||||
|
||||
```
|
||||
HEAD@{2024-01-01}..HEAD@{2025-01-01}
|
||||
```
|
||||
|
||||
Putting that all together, we this command and potentially a big list of files.
|
||||
|
||||
```bash
|
||||
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01}
|
||||
```
|
||||
|
||||
I wanted to restrict the results to just markdown files, so I added a filename
|
||||
pattern.
|
||||
|
||||
```bash
|
||||
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01} -- "*.md"
|
||||
```
|
||||
|
||||
I could even go a step further to see only the files added to a specific
|
||||
directory.
|
||||
|
||||
```bash
|
||||
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01} -- "postgres/*.md"
|
||||
```
|
||||
|
||||
As a final bonus, I can spit out the github URLs for all those files with a bit of `awk`.
|
||||
|
||||
```bash
|
||||
$ git diff --diff-filter=A --name-only HEAD@{2024-01-01}..HEAD@{2025-01-01} -- "postgres/*.md" |
|
||||
awk '{print "https://github.com/jbranchaud/til/blob/master/" $0}'
|
||||
```
|
||||
28
git/list-all-git-aliases-from-gitconfig.md
Normal file
28
git/list-all-git-aliases-from-gitconfig.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# List All Git Aliases From gitconfig
|
||||
|
||||
Running the `git config --list` command will show all of the configuration
|
||||
settings you have for `git` relative to your current location. Though most of
|
||||
these setting probably live in `~/.gitconfig`, you may also have some locally
|
||||
specified ones in `.git/config`. This will grab them all including any `alias`
|
||||
entries.
|
||||
|
||||
We can narrow things down to just `alias` entries using the `--get-regexp` flag.
|
||||
|
||||
```bash
|
||||
$ git config --get-regexp '^alias\.'
|
||||
|
||||
alias.ap add --patch
|
||||
alias.authors shortlog -s -n -e
|
||||
alias.co checkout
|
||||
alias.st status
|
||||
alias.put push origin HEAD
|
||||
alias.fixup commit --fixup
|
||||
alias.squash commit --squash
|
||||
alias.doff reset HEAD^
|
||||
alias.add-untracked !git status --porcelain | awk '/\?\?/{ print $2 }' | xargs git add
|
||||
alias.reset-authors commit --amend --reset-author -CHEAD
|
||||
```
|
||||
|
||||
I use `git doff` all the time on feature branches to "pop" the latest commmit
|
||||
onto the working copy. I was trying to remember exactly what the `git doff`
|
||||
command is and this was an easy way to check.
|
||||
33
git/override-the-global-git-ignore-file.md
Normal file
33
git/override-the-global-git-ignore-file.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Override The Global Git Ignore File
|
||||
|
||||
One of the places that `git` looks when deciding whether to pay attention to or
|
||||
ignore a file is in your global _ignore_ file. By default, `git` will look for
|
||||
this file at `$XDG_CONFIG_HOME/git/ignore` or `$HOME/.config/git/ignore`.
|
||||
|
||||
I don't have `$XDG_CONFIG_HOME` set on my machine, so it will fall back to the
|
||||
config directory under `$HOME`.
|
||||
|
||||
I may have to create the `git` directory and `ignore` file.
|
||||
|
||||
```bash
|
||||
$ mkdir $HOME/.config/git
|
||||
$ touch $HOME/.config/git/ignore
|
||||
```
|
||||
|
||||
Then I can add file and directories to exclude to that `ignore` file just like
|
||||
I would any other `.gitignore` file.
|
||||
|
||||
If I'd prefer for the global _ignore_ file to live somewhere else, I can
|
||||
specify that location and filename in my `$HOME/.gitconfig` file.
|
||||
|
||||
```
|
||||
[core]
|
||||
excludesFile = ~/.gitignore
|
||||
```
|
||||
|
||||
Setting this will override the default, meaning the default file mentioned
|
||||
above will be ignored ("now you know how it feels, ignore file!"). In this
|
||||
case, I'll need to create the `.gitignore` file in my home directory and add
|
||||
any of my ignore rules.
|
||||
|
||||
[source](https://git-scm.com/docs/gitignore)
|
||||
34
git/reference-commits-earlier-than-reflog-remembers.md
Normal file
34
git/reference-commits-earlier-than-reflog-remembers.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Reference Commits Earlier Than Reflog Remembers
|
||||
|
||||
While preparing some stats for a recent blog post on [A Decade of
|
||||
TILs](https://www.visualmode.dev/a-decade-of-tils), I ran into an issue
|
||||
referencing chunks of time further back than 2020.
|
||||
|
||||
```bash
|
||||
❯ git diff --diff-filter=A --name-only HEAD@{2016-02-06}..HEAD@{2017-02-06} -- "*.md"
|
||||
warning: log for 'HEAD' only goes back to Sun, 20 Dec 2020 00:26:27 -0600
|
||||
warning: log for 'HEAD' only goes back to Sun, 20 Dec 2020 00:26:27 -0600
|
||||
```
|
||||
|
||||
This is because `HEAD@...` is a reference to the `reflog`. The `reflog` is a
|
||||
local-only log of objects and activity in the repository. That date looks
|
||||
suspiciously like the time that I got this specific machine and cloned the
|
||||
repo.
|
||||
|
||||
In order to access this information, I need a different approach of finding
|
||||
references that bound these points in time.
|
||||
|
||||
How about asking `rev-list` for the first commit it can find before the given
|
||||
dates in 2017 and 2016 and then using those.
|
||||
|
||||
```bash
|
||||
❯ git rev-list -1 --before="2017-02-07 00:00" HEAD
|
||||
17db6bc4468616786a8f597a10d252c24183d82e
|
||||
|
||||
❯ git rev-list -1 --before="2016-02-07 00:00" HEAD
|
||||
f1d3d1f796007662ff448d6ba0e3bbf38a2b858d
|
||||
|
||||
❯ git diff --diff-filter=A --name-only f1d3d1f796007662ff448d6ba0e3bbf38a2b858d..17db6bc4468616786a8f597a10d252c24183d82e -- "*.md"
|
||||
|
||||
# git outputs a bunch of files ...
|
||||
```
|
||||
26
git/remove-untracked-files-from-a-directory.md
Normal file
26
git/remove-untracked-files-from-a-directory.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Remove Untracked Files From A Directory
|
||||
|
||||
Let's say I have a directory (`spec/cassettes`) full of a ton of generated YAML
|
||||
files. Most of these files are tracked by git. However, I just generated a
|
||||
bunch of new ones that are untracked. For whatever reason, I don't want these
|
||||
files. I need to delete them.
|
||||
|
||||
Running `rm` on each of them is going to be too tedious. And it is tricky to
|
||||
target them for a bulk delete since there are a ton of other files in that
|
||||
directory that I want to keep.
|
||||
|
||||
One way to approach this is have `git ls-files` help out with listing all files in the
|
||||
directory that are untracked. The `--others` flag filters to untracked files.
|
||||
|
||||
```bash
|
||||
git ls-files --others --exclude-standard spec/cassettes
|
||||
```
|
||||
|
||||
From there, I can pipe it to `rm` (with `xargs` collapsing all the files into a
|
||||
single line):
|
||||
|
||||
```bash
|
||||
git ls-files --others --exclude-standard spec/cassettes | xargs rm
|
||||
```
|
||||
|
||||
See `man git-ls-files` for more details.
|
||||
20
git/restore-file-from-one-branch-to-the-current.md
Normal file
20
git/restore-file-from-one-branch-to-the-current.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Restore File From One Branch To The Current
|
||||
|
||||
On one feature branch I have created some files and made changes to some
|
||||
existing files as part of spiking a feature. Now I'm on a different branch
|
||||
taking another shot at it. I want changes from one or two of the files. In the
|
||||
past I've used `git-checkout` for this task. However, I believe this is one of
|
||||
the use cases they had in mind when they added `git-restore`.
|
||||
|
||||
What I want to do is _restore_ the state of a file as it appears on some source
|
||||
branch to my current branch. Here is what that looks like:
|
||||
|
||||
```bash
|
||||
$ git restore --source=some-feature-branch app/models/contact.rb
|
||||
```
|
||||
|
||||
Now when I check `git status` I'll see the state of that file on the
|
||||
`some-feature-branch` branch overlayed on my current working copy. If the file
|
||||
doesn't exist, it will be created.
|
||||
|
||||
See `man git-restore` for more details.
|
||||
28
git/review-commits-from-before-a-certain-date.md
Normal file
28
git/review-commits-from-before-a-certain-date.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Review Commits From Before A Certain Date
|
||||
|
||||
I was recently looking at data in a 3rd-party tool and saw that there was a
|
||||
very distinct shift in what was being recorded a couple years prior on a
|
||||
specific date. I wanted to see what changes had been made to the codebase a day
|
||||
or two before the shift.
|
||||
|
||||
Rather than scrolling all the way back in `git log`, I can tell `git log` to
|
||||
show me all commits from before a certain date.
|
||||
|
||||
Let's say that date of interest is May 1st, 2021. I can use the `--until` flag
|
||||
with `git log`. However, I should note that `--until` is an exclusive range, so
|
||||
I'll need to specify `May 2 2021` if I want to start seeing commits on May 1.
|
||||
|
||||
```bash
|
||||
$ git log --until='May 2 2021'
|
||||
```
|
||||
|
||||
Because `git log` shows commits in reverse chronological order, I'll start
|
||||
seeing commits from May 1st and then as I scroll, I'll see older and older
|
||||
commits.
|
||||
|
||||
From here I can scan commits messages and look for one that I want to dig into.
|
||||
I'd then use `git show <sha>` to explore a specific one further.
|
||||
|
||||
This is synonymous with `--before`.
|
||||
|
||||
See `man git-log` for more details.
|
||||
22
git/set-default-branch-name-for-new-repos.md
Normal file
22
git/set-default-branch-name-for-new-repos.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# Set Default Branch Name For New Repos
|
||||
|
||||
When you run `git init` in a directory, it will initialize that directory as a
|
||||
`git` repository. The default branch name for any new git repository is
|
||||
`master`. A better name for the base branch would be something like `main`.
|
||||
|
||||
To set `main` as the new default for all new repos, you can run the following
|
||||
`git` command.
|
||||
|
||||
```bash
|
||||
$ git config --global init.defaultBranch main
|
||||
```
|
||||
|
||||
This will update your `.gitconfig` file to contain the following lines.
|
||||
|
||||
```
|
||||
[init]
|
||||
defaultBranch = main
|
||||
```
|
||||
|
||||
Try running `git init` in a fresh directory and then `git branch
|
||||
--show-current` to see that the base branch is now defaulting to `main`.
|
||||
56
git/set-up-gpg-signing-key.md
Normal file
56
git/set-up-gpg-signing-key.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Set Up GPG Signing Key
|
||||
|
||||
I wanted to have that "Verified" icon start showing up next to my commits in
|
||||
GitHub. To do that, I need to generate a GPG key, configure the secret key in
|
||||
GitHub, and then configure the public signing key with my git config.
|
||||
|
||||
```bash
|
||||
# generate a gpg key
|
||||
$ gpg --full-generate-key
|
||||
|
||||
# Pick the following options when prompted
|
||||
# - Choose "RSA and RSA" (Options 1)
|
||||
# - Max out key size at 4096
|
||||
# - Choose expiration date (e.g. 0 for no expiration)
|
||||
# - Enter "Real name" and "Email"
|
||||
(I matched those to what is in my global git config)
|
||||
# - Set passphrase (I had 1password generate a 4-word passphrase)
|
||||
```
|
||||
|
||||
It may take a few seconds to create.
|
||||
|
||||
I can see it was created by listing my GPG keys.
|
||||
|
||||
```bash
|
||||
$ gpg --list-secret-keys --keyid-format=long
|
||||
[keyboxd]
|
||||
---------
|
||||
sec rsa4096/1A8656918A8D016B 2025-10-19 [SC]
|
||||
...
|
||||
```
|
||||
|
||||
I'll need the `1A8656918A8D016B` portion of that response for the next command
|
||||
and it is what I set as my public signing key in my git config.
|
||||
|
||||
First, though, I add the full key block to my GitHub profile which I can copy
|
||||
like so:
|
||||
|
||||
```bash
|
||||
$ gpg --armor --export 1A8656918A8D016B | pbcopy
|
||||
```
|
||||
|
||||
And then I paste that as a new GPG Key on GitHub under _Settings_ -> _SSH and
|
||||
GPG Keys_.
|
||||
|
||||
Last, I update my global git config with the signing key and the preference to
|
||||
sign commits:
|
||||
|
||||
```bash
|
||||
git config --global user.signingkey 1A8656918A8D016B
|
||||
git config --global commit.gpgsign true
|
||||
```
|
||||
|
||||
Without `commit.gpgsign`, I would have to specify the `-S` flag every time I
|
||||
want to create a signed commit.
|
||||
|
||||
[source](https://git-scm.com/book/ms/v2/Git-Tools-Signing-Your-Work)
|
||||
26
git/show-summary-stats-for-current-branch.md
Normal file
26
git/show-summary-stats-for-current-branch.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Show Summary Stats For Current Branch
|
||||
|
||||
When I push a branch up to GitHub as a PR, there is a part of the UI that shows
|
||||
you how many lines you've added and removed for this branch. It bases that off
|
||||
the target branch which is typically your `main` branch.
|
||||
|
||||
The `git diff` command can provide those same stats right in the terminal. The
|
||||
key is to specify the `--shortstat` flag which tells `git` to exclude other diff
|
||||
output and only show:
|
||||
|
||||
- Number of files changed
|
||||
- Number of insertions
|
||||
- Number of deletions
|
||||
|
||||
Here is the summary stats for a branch I'm working on:
|
||||
|
||||
```bash
|
||||
❯ git diff --shortstat main
|
||||
8 files changed, 773 insertions(+), 25 deletions(-)
|
||||
```
|
||||
|
||||
We have to be on our feature branch and then we point to the branch (or whatever
|
||||
ref) we want to diff against. Since I want to know how my feature branch
|
||||
compares to `main`, I specify that.
|
||||
|
||||
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`.
|
||||
@@ -0,0 +1,43 @@
|
||||
# Cache Playwright Dependencies Across Workflows
|
||||
|
||||
With the help of `actions/cache@v3`, I can cache the dependency install and
|
||||
setup involved with using Playwright in GitHub Actions. That setup, in my
|
||||
experience, typically takes ~45s. When it is already cached, it is able to skip
|
||||
that step entirely greatly reducing the overall run time of the script.
|
||||
|
||||
First, I need to define a cache (`playwright-cache`). Second, I need to only
|
||||
install the Playwright dependencies when that cache isn't available (`cache-hit
|
||||
!= 'true'`).
|
||||
|
||||
Here is a striped down workflow demonstrating that.
|
||||
|
||||
```yaml
|
||||
name: Playwright Script
|
||||
on:
|
||||
workflow_dispatch:
|
||||
jobs:
|
||||
Cached-Playwright-Script:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out repository code
|
||||
uses: actions/checkout@v3
|
||||
- uses: actions/cache@v3
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('**/package-lock.json') }}
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Install playwright deps
|
||||
run: npx playwright install --with-deps chromium
|
||||
if: steps.playwright-cache.outputs.cache-hit != 'true'
|
||||
- run: node playwright-script.js
|
||||
```
|
||||
|
||||
If I add the caching step and the cache-conditional `playwright install` steps
|
||||
to another workflow in this project, the cache will be available to both of
|
||||
them. That means they both benefit from the savings of that work having already
|
||||
been cached.
|
||||
|
||||
[source](https://justin.poehnelt.com/posts/caching-playwright-in-github-actions/)
|
||||
41
github-actions/disable-a-workflow-with-the-gh-cli.md
Normal file
41
github-actions/disable-a-workflow-with-the-gh-cli.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Disable A Workflow With The gh CLI
|
||||
|
||||
You may want to temporarily disable a GitHub Actions workflow without deleting
|
||||
the file for the workflow. In my case, this is handy because I want to keep a
|
||||
scheduled workflow around as a point of reference, but I don't want it running
|
||||
all the time.
|
||||
|
||||
This can be done with [the `workflow` subcommand of the `gh`
|
||||
CLI](https://docs.github.com/en/actions/using-workflows/disabling-and-enabling-a-workflow?tool=cli).
|
||||
|
||||
First, list the workflows for your current repo so that you can figure out the
|
||||
workflow ID that you want to disable.
|
||||
|
||||
```bash
|
||||
$ gh workflow list
|
||||
GitHub Actions Demo active 60018591
|
||||
Playwright Demo active 60142509
|
||||
Scheduled Actions Demo active 60028624
|
||||
```
|
||||
|
||||
Now, copy the ID of the workflow you want to disable. In my case, it is
|
||||
`60028624`.
|
||||
|
||||
Then, run the `disable` command for that workflow ID:
|
||||
|
||||
```bash
|
||||
$ gh workflow disable 60028624
|
||||
✓ Disabled Scheduled Actions Demo
|
||||
```
|
||||
|
||||
That workflow is now disabled and it is no longer going to show up in the
|
||||
default listing of workflows.
|
||||
|
||||
If you want to see it in the list though, you can include the `--all` flag.
|
||||
|
||||
```bash
|
||||
$ gh workflow list --all
|
||||
GitHub Actions Demo active 60018591
|
||||
Playwright Demo active 60142509
|
||||
Scheduled Actions Demo disabled_manually 60028624
|
||||
```
|
||||
37
github-actions/trigger-a-workflow-via-an-api-call.md
Normal file
37
github-actions/trigger-a-workflow-via-an-api-call.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Trigger A Workflow Via An API Call
|
||||
|
||||
We can set up a GitHub Actions workflow to run when triggered by an API call.
|
||||
This is done with the [`workflow_dispatch`
|
||||
event](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#workflow_dispatch).
|
||||
|
||||
First, we add `workflow_dispatch` to our workflow as a triggering event:
|
||||
|
||||
```yaml
|
||||
on:
|
||||
workflow_dispatch:
|
||||
```
|
||||
|
||||
Second, we create a fine-grained personal GitHub access token that has permissions
|
||||
for dispatching to GitHub Actions. More details on that in the [GitHub
|
||||
docs](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens#creating-a-fine-grained-personal-access-token).
|
||||
|
||||
Then, we can use `cURL` or some other tool for issuing an HTTP POST request to
|
||||
[the workflow dispatch API
|
||||
endpoint](https://docs.github.com/en/rest/actions/workflows?apiVersion=2022-11-28#create-a-workflow-dispatch-event). The `cURL` request will look something like this:
|
||||
|
||||
```bash
|
||||
curl -L \
|
||||
-X POST \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
-H "Authorization: Bearer <YOUR-TOKEN>"\
|
||||
-H "X-GitHub-Api-Version: 2022-11-28" \
|
||||
https://api.github.com/repos/OWNER/REPO/actions/workflows/WORKFLOW_ID/dispatches \
|
||||
-d '{"ref":"topic-branch","inputs":{"name":"Mona the Octocat","home":"San Francisco, CA"}}'
|
||||
```
|
||||
|
||||
Note: we need to alter that URL with the `OWNER` and `REPO` that the workflow
|
||||
lives in as well as the `WORKFLOW_ID` which can be the name of the workflow
|
||||
file (e.g. `my-dispatchable-workflow.yml`).
|
||||
|
||||
This event also means that we can manually trigger the workflow from the
|
||||
GitHub Actions UI for that workflow.
|
||||
41
github-actions/use-labels-to-block-pr-merge.md
Normal file
41
github-actions/use-labels-to-block-pr-merge.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Use Labels To Block PR Merge
|
||||
|
||||
Let's say our GitHub project has custom tags for both `no merge` and `wip`
|
||||
(_work in progress_). Whenever either of those labels has been applied to a PR,
|
||||
we want there to be a failed check so as to block the merge. This is useful to
|
||||
ensure automated tools (as well as someone not looking closely enough) don't
|
||||
merge a PR that isn't _ready to go_.
|
||||
|
||||
This can be achieved with a basic GitHub Actions workflow that requires no
|
||||
3rd-party actions. We can add the following as
|
||||
`.github/workflows/block-labeled-prs.yml` in our project.
|
||||
|
||||
```yaml
|
||||
name: Block Labeled PR Merges
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [labeled, unlabeled, opened, edited, synchronize]
|
||||
|
||||
jobs:
|
||||
prevent-merge:
|
||||
if: ${{ contains(github.event.*.labels.*.name, 'no merge') || contains(github.event.*.labels.*.name, 'wip') }}
|
||||
name: Prevent Merging
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check for label
|
||||
run: |
|
||||
echo "Pull request label prevents merging."
|
||||
echo "Labels: ${{ join(github.event.*.labels.*.name, ', ') }}"
|
||||
echo "Remove the blocking label(s) to skip this check."
|
||||
exit 1
|
||||
```
|
||||
|
||||
This workflow is run when a pull request is opened, when it is edited or
|
||||
synchronized, and when a label change is made. The job `prevent-merge` sees if
|
||||
any of the label names match `no merge` or `wip`. If so, we echo out some
|
||||
details in the ubuntu container and then `exit 1` to fail the check.
|
||||
|
||||
Shoutout to [Jesse Squire's
|
||||
implementation](https://www.jessesquires.com/blog/2021/08/24/useful-label-based-github-actions-workflows/#updated-21-march-2022)
|
||||
which I've heavily borrowed from here.
|
||||
25
github/access-your-github-profile-photo.md
Normal file
25
github/access-your-github-profile-photo.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Access Your GitHub Profile Photo
|
||||
|
||||
Let's say I have my [GitHub profile](https://github.com/jbranchaud) pulled up in
|
||||
the browser.
|
||||
|
||||
```
|
||||
https://github.com/jbranchaud
|
||||
```
|
||||
|
||||
If I then add `.png` to the end of that in the URL bar:
|
||||
|
||||
```
|
||||
https://github.com/jbranchaud.png
|
||||
```
|
||||
|
||||
I'll be redirected to the URL where the full image file lives. In my case:
|
||||
|
||||
```
|
||||
https://avatars.githubusercontent.com/u/694063?v=4
|
||||
```
|
||||
|
||||
You can pull up yours `https://github.com/<username>.png` to access your profile
|
||||
image.
|
||||
|
||||
[source](https://dev.to/10xlearner/how-to-get-the-profile-picture-of-a-github-account-1d82)
|
||||
19
github/open-a-pr-to-an-unforked-repo.md
Normal file
19
github/open-a-pr-to-an-unforked-repo.md
Normal file
@@ -0,0 +1,19 @@
|
||||
# Open A PR To An Unforked Repo
|
||||
|
||||
Sometimes I will clone a repo to explore the source code or to look into a
|
||||
potential bug. If my curiosity takes me far enough to make some changes, then I
|
||||
jump through the hoops of creating a fork, reconfiguring branches, pushing to my
|
||||
fork, and then opening the branch as a PR against the original repo.
|
||||
|
||||
The `gh` CLI allows me to avoid all that hoop-jumping. Directly from the cloned
|
||||
repo I can use `gh` to create a new PR. It will prompt me to creat a fork. If I
|
||||
accept, it will seamlessly create it and then open a PR from my fork to the
|
||||
original.
|
||||
|
||||
```bash
|
||||
$ gh pr create
|
||||
```
|
||||
|
||||
This allows me to create the PR with a few prompts from the CLI. If you prefer,
|
||||
you can include the `--web` flag to open the PR creation screen directly in the
|
||||
browser.
|
||||
20
github/target-another-repo-when-creating-a-pr.md
Normal file
20
github/target-another-repo-when-creating-a-pr.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Target Another Repo When Creating A PR
|
||||
|
||||
I have a [`dotfiles` repo](https://github.com/jbranchaud/dotfiles) that I forked
|
||||
from [`dkarter/dotfiles`](https://github.com/dkarter/dotfiles). I'm adding a
|
||||
bunch of my own customizations on a `main` branch while continually pulling in
|
||||
and merging upstream changes.
|
||||
|
||||
The primary remote according to `gh` is `jbranchaud/dotfiles`. 98% of the time
|
||||
that is what I want. However, I occasionally want to share some changes upstream
|
||||
via a PR. Running `gh pr create` as is will create a PR against my fork. To
|
||||
override this on a one-off basis, I can use the `--repo` flag.
|
||||
|
||||
```bash
|
||||
$ gh pr create --repo dkarter/dotfiles
|
||||
```
|
||||
|
||||
This will create a PR against `dkarter:master` from my branch (e.g.
|
||||
[`jbranchaud:jb/fix-hardcoded-paths`](https://github.com/dkarter/dotfiles/pull/373)).
|
||||
|
||||
See `man gh-pr-create` for more details.
|
||||
38
github/tell-gh-what-the-default-repo-is.md
Normal file
38
github/tell-gh-what-the-default-repo-is.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Tell gh What The Default Repo Is
|
||||
|
||||
I recently forked [dkarter/dotfiles](https://github.com/dkarter/dotfiles) as a
|
||||
way of bootstrapping a robust dotfile config for a new machine that I could
|
||||
start making customizations to. I'm maintaining a `my-dotfiles` branch and keep
|
||||
things in sync with the original upstream repo.
|
||||
|
||||
When trying to go to *my* fork of the repo
|
||||
([jbranchaud/dotfiles](https://github.com/jbranchaud/dotfiles)) in the web with
|
||||
the `gh` CLI tool, I ran into a weird issue. It was instead opening up to
|
||||
`dkarter/dotfiles`.
|
||||
|
||||
`gh` was under the wrong impression which repo should be considered the default.
|
||||
To clarify things for `gh`, there is a command to set the default repo.
|
||||
|
||||
```bash
|
||||
$ gh repo set-default jbranchaud/dotfiles
|
||||
✓ Set jbranchaud/dotfiles as the default repository for the current directory
|
||||
```
|
||||
|
||||
Now when I run `gh repo view --web`, it opens the browser to my fork of the
|
||||
dotfiles.
|
||||
|
||||
But where does this setting live?
|
||||
|
||||
Opening this repo's `.git/config` file I can see a section for the `origin`
|
||||
remote that includes a new line for `gh-resolved`. This being set to `base`
|
||||
tells `gh` that this remote is the one to treat as the default repo.
|
||||
|
||||
```
|
||||
[remote "origin"]
|
||||
url = git@github.com:jbranchaud/dotfiles.git
|
||||
fetch = +refs/heads/*:refs/remotes/origin/*
|
||||
gh-resolved = base
|
||||
|
||||
```
|
||||
|
||||
See `gh repo set-default --help` for more details.
|
||||
@@ -15,4 +15,10 @@ $ godoc -http=:6060
|
||||
|
||||
and then visit `localhost:6060`.
|
||||
|
||||
Note: if you do not already have `godoc` installed, you can install it with:
|
||||
|
||||
```bash
|
||||
$ go install golang.org/x/tools/cmd/godoc@latest
|
||||
```
|
||||
|
||||
[source](http://www.andybritcliffe.com/post/44610795381/offline-go-lang-documentation)
|
||||
|
||||
70
go/add-a-method-to-a-struct.md
Normal file
70
go/add-a-method-to-a-struct.md
Normal file
@@ -0,0 +1,70 @@
|
||||
# Add A Method To A Struct
|
||||
|
||||
Given a `struct` in Go, we can attach a method to that struct. Put another way,
|
||||
we can define a method whose receiver is that struct. Then with an instance of
|
||||
that struct, we can call the method.
|
||||
|
||||
Let's say we are modeling a turtle that can move around a 2D grid. A turtle has
|
||||
a heading (the direction it is headed) and a location (its current X,Y
|
||||
coordinate).
|
||||
|
||||
```go
|
||||
type Heading string
|
||||
|
||||
const (
|
||||
UP Heading = "UP"
|
||||
RIGHT Heading = "RIGHT"
|
||||
DOWN Heading = "DOWN"
|
||||
LEFT Heading = "LEFT"
|
||||
)
|
||||
|
||||
type Turtle struct {
|
||||
Direction Heading
|
||||
X int
|
||||
Y int
|
||||
}
|
||||
```
|
||||
|
||||
We can then add a method like so by specifying the receiver as the first part
|
||||
of the declaration:
|
||||
|
||||
```go
|
||||
func (turtle *Turtle) TurnRight() {
|
||||
switch turtle.Direction {
|
||||
case UP:
|
||||
turtle.Direction = RIGHT
|
||||
case RIGHT:
|
||||
turtle.Direction = DOWN
|
||||
case DOWN:
|
||||
turtle.Direction = LEFT
|
||||
case LEFT:
|
||||
turtle.Direction = UP
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The receiver is a pointer to a `Turtle`. The method is called `TurnRight`.
|
||||
There are no parameters or return values.
|
||||
|
||||
Here are a sequence of calls to demonstrate how it works:
|
||||
|
||||
```go
|
||||
func main() {
|
||||
turtle := Turtle{UP, 5, 5}
|
||||
|
||||
fmt.Println("Turtle Direction:", turtle.Direction)
|
||||
//=> Turtle Direction: UP
|
||||
|
||||
turtle.TurnRight()
|
||||
|
||||
fmt.Println("Turtle Direction:", turtle.Direction)
|
||||
//=> Turtle Direction: RIGHT
|
||||
|
||||
turtle.TurnRight()
|
||||
|
||||
fmt.Println("Turtle Direction:", turtle.Direction)
|
||||
//=> Turtle Direction: DOWN
|
||||
}
|
||||
```
|
||||
|
||||
[source](https://go.dev/tour/methods/1)
|
||||
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.
|
||||
51
go/combine-two-slices.md
Normal file
51
go/combine-two-slices.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Combine Two Slices
|
||||
|
||||
The `append` function can be used to create a new slice with the contents of
|
||||
the given slice and one or more items added to the end.
|
||||
|
||||
We can add one or more items like so:
|
||||
|
||||
```go
|
||||
s1 := []int{1, 2, 3, 4}
|
||||
s2 := append(s1, 5)
|
||||
s3 := append(s2, 6, 7, 8)
|
||||
|
||||
fmt.Println(s1) //=> [1 2 3 4]
|
||||
fmt.Println(s2) //=> [1 2 3 4 5]
|
||||
fmt.Println(s3) //=> [1 2 3 4 5 6 7 8]
|
||||
```
|
||||
|
||||
But what if we have a second slice instead of individual items? We could import
|
||||
`slices` and use its `Concat` function. Or we can stick with `append` and
|
||||
unpack that slice as a series of arguments into the second part of `append`
|
||||
using `slice...`.
|
||||
|
||||
```go
|
||||
s4 := append(s2, s1...)
|
||||
fmt.Println(s4) //=> [1 2 3 4 5 1 2 3 4]
|
||||
```
|
||||
|
||||
Here is the full example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s1 := []int{1, 2, 3, 4}
|
||||
s2 := append(s1, 5)
|
||||
s3 := append(s2, 6, 7, 8)
|
||||
|
||||
fmt.Println(s1)
|
||||
fmt.Println(s2)
|
||||
fmt.Println(s3)
|
||||
|
||||
s4 := append(s2, s1...)
|
||||
fmt.Println(s4)
|
||||
}
|
||||
```
|
||||
|
||||
[source](https://pkg.go.dev/builtin#append)
|
||||
29
go/configure-max-string-print-length-for-delve.md
Normal file
29
go/configure-max-string-print-length-for-delve.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Configure Max String Print Length For Delve
|
||||
|
||||
During a [Delve](https://github.com/go-delve/delve) debugging session, we can
|
||||
print out the value of a given variable with the `print` command. Similarly, we
|
||||
can see the values of all local variables with the `locals` command.
|
||||
|
||||
Whenever Delve is printing out strings and slices, it will truncate what it
|
||||
displays to 64 characters (or items) by default.
|
||||
|
||||
```go
|
||||
(dlv) print diagnostics.Solutions[0]
|
||||
"295743861\n431865972\n876192543\n387459216\n612387495\n549216738\n7635...+25 more"
|
||||
```
|
||||
|
||||
This can be overridden by [changing the `config` of
|
||||
`max-string-len`](https://github.com/derekparker/delve/blob/237c5026f40e38d2dd6f62a7362de7b25b00c1c7/Documentation/cli/expr.md?plain=1#L59)
|
||||
to something longer. In my case here, all I need are about 90 characters to
|
||||
display my full string, so run `config max-string-len 90` from the `dlv`
|
||||
session.
|
||||
|
||||
```go
|
||||
(dlv) config max-string-len 90
|
||||
(dlv) print diagnostics.Solutions[0]
|
||||
"295743861\n431865972\n876192543\n387459216\n612387495\n549216738\n763524189\n928671354\n154938627"
|
||||
```
|
||||
|
||||
Now I can see the entire string instead of the truncated version.
|
||||
|
||||
[source](https://stackoverflow.com/a/52416264/535590)
|
||||
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.
|
||||
55
go/difference-between-slice-and-pointer-to-slice.md
Normal file
55
go/difference-between-slice-and-pointer-to-slice.md
Normal file
@@ -0,0 +1,55 @@
|
||||
# Difference Between Slice And Pointer To Slice
|
||||
|
||||
Though a slice can be thought of and used as a flexible, variable-length
|
||||
array-like data structure, it is important to understand that it is also a
|
||||
special kind of pointer to an underlying array.
|
||||
|
||||
This matters when we a function receives a slice versus a pointer to a slice as
|
||||
an argument, depending on what it is doing with that slice.
|
||||
|
||||
If the function is access or updating elements in the slice, there is no
|
||||
difference. There is no meaningful difference between these two functions and
|
||||
we might as well use the former.
|
||||
|
||||
```go
|
||||
func replaceAtIndex(slice []string, index int, value string) {
|
||||
slice[index] = value
|
||||
}
|
||||
|
||||
func replaceAtIndexPtr(slice *[]string, index int, value string) {
|
||||
(*slice)[index] = value
|
||||
}
|
||||
```
|
||||
|
||||
On the other hand, if the receiving function needs to append to or replace the
|
||||
slice, then we need to pass a pointer to the slice. A direct slice argument
|
||||
will result in only the function-local copy getting replaced.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
s1 := []int{8, 6, 7, 9}
|
||||
s2 := []int{8, 6, 7, 9}
|
||||
|
||||
addItem(s1, 11)
|
||||
fmt.Printf("s1: %v\n", s1) //=> s1: [8 6 7 9]
|
||||
|
||||
addItemPtr(&s2, 11)
|
||||
fmt.Printf("s2: %v\n", s2) //=> s2: [8 6 7 9 11]
|
||||
}
|
||||
|
||||
func addItem(slice []int, value int) {
|
||||
slice = append(slice, value)
|
||||
}
|
||||
|
||||
func addItemPtr(slice *[]int, value int) {
|
||||
(*slice) = append(*slice, value)
|
||||
}
|
||||
```
|
||||
|
||||
[source](https://go.dev/tour/moretypes/8)
|
||||
56
go/do-something-n-times.md
Normal file
56
go/do-something-n-times.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# Do Something N Times
|
||||
|
||||
With Go 1.23 there is a new for-range syntax that makes looping a bit easier
|
||||
and more compact.
|
||||
|
||||
Instead of needing to set up our 3-part for-loop syntax, we can say we want to
|
||||
do something `N` times with `for range N`.
|
||||
|
||||
```go
|
||||
for range n {
|
||||
// do something
|
||||
}
|
||||
```
|
||||
|
||||
Let's look at an actual, runnable example:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
import "math/rand"
|
||||
import "time"
|
||||
|
||||
func main() {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
food := []string{"taco", "burrito", "torta", "enchilada", "tostada"}
|
||||
|
||||
for range 5 {
|
||||
randomIndex := rand.Intn(len(food))
|
||||
fmt.Println(food[randomIndex])
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The output is random and might look something like this:
|
||||
|
||||
```bash
|
||||
$ go run loop.go
|
||||
taco
|
||||
burrito
|
||||
tostada
|
||||
taco
|
||||
enchilada
|
||||
```
|
||||
|
||||
I appreciate this syntax addition because it feels very akin to Ruby's `#times`
|
||||
method:
|
||||
|
||||
```ruby
|
||||
5.times do
|
||||
# do something
|
||||
end
|
||||
```
|
||||
|
||||
[source](https://eli.thegreenplace.net/2024/ranging-over-functions-in-go-123/)
|
||||
26
go/find-executables-installed-by-go.md
Normal file
26
go/find-executables-installed-by-go.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# Find Executables Installed By Go
|
||||
|
||||
When you install an executable using `go install`, it puts that executable in
|
||||
the `bin` directory designated by the `GOBIN` environment variable. If that env
|
||||
var isn't set, then it falls back to one of `$GOPATH/bin` or `$HOME/go/bin`.
|
||||
|
||||
When I run `go help install`, it tells me as much:
|
||||
|
||||
```
|
||||
Executables are installed in the directory named by the GOBIN environment
|
||||
variable, which defaults to $GOPATH/bin or $HOME/go/bin if the GOPATH
|
||||
environment variable is not set.
|
||||
```
|
||||
|
||||
So, if I am to install something like [`tern`](https://github.com/jackc/tern),
|
||||
|
||||
```bash
|
||||
$ go install github.com/jackc/tern/v2@latest
|
||||
```
|
||||
|
||||
it is going to place that binary in `~/go/bin` for me.
|
||||
|
||||
```bash
|
||||
$ which tern
|
||||
/Users/jbranchaud/go/bin/tern
|
||||
```
|
||||
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`.
|
||||
39
go/parse-a-string-into-individual-fields.md
Normal file
39
go/parse-a-string-into-individual-fields.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# Parse A String Into Individual Fields
|
||||
|
||||
Let's say you're reading in data from a file or otherwise dealing with an
|
||||
arbitrary string of data. If that string has a series of values separated by
|
||||
whitespace, you can parse it into individual fields with
|
||||
[`strings.Fields`](https://pkg.go.dev/strings#Fields).
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := "3 5 2 6 7 1 9"
|
||||
fields := strings.Fields(data)
|
||||
|
||||
fmt.Printf("Fields: %v", fields)
|
||||
// [3 5 2 6 7 1 9]
|
||||
}
|
||||
```
|
||||
|
||||
Here is another example where we can see that `strings.Fields` deals with
|
||||
multiple whitespace and surrounding whitespace:
|
||||
|
||||
```go
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data := " go java c++ rust "
|
||||
fields := strings.Fields(data)
|
||||
|
||||
fmt.Printf("%v", fields)
|
||||
// [go java c++ rust]
|
||||
}
|
||||
```
|
||||
65
go/parse-flags-from-cli-arguments.md
Normal file
65
go/parse-flags-from-cli-arguments.md
Normal file
@@ -0,0 +1,65 @@
|
||||
# Parse Flags From CLI Arguments
|
||||
|
||||
Though we can grab the arguments to a Go program from `os.Args`, it requires
|
||||
some manual parsing. With the built-in `flag` package, we can declare specific
|
||||
flags our program accepts, by type. When we parse them, they will be separated
|
||||
out from the rest of the positional arguments.
|
||||
|
||||
Here is an example of the program that accepts a boolean `debug` flag. This
|
||||
will work with either `-debug` or `--debug`.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var debug bool
|
||||
flag.BoolVar(&debug, "debug", false, "turns on debug mode, extra logging")
|
||||
flag.Parse()
|
||||
|
||||
positionalArgs := flag.Args()
|
||||
|
||||
if len(positionalArgs) < 1 {
|
||||
fmt.Println("Please specify which part to run: 1 or 2")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if debug {
|
||||
fmt.Println("We are in debug mode...")
|
||||
fmt.Println("Received the following argument:", positionalArgs[0])
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
We can run the program in debug mode like so:
|
||||
|
||||
```bash
|
||||
$ go run . --debug 123
|
||||
We are in debug mode...
|
||||
Received the following argument: 123
|
||||
```
|
||||
|
||||
We can also take advantage of the `help` flag that we get for free:
|
||||
|
||||
```bash
|
||||
$ go run . --help
|
||||
Usage of /var/folders/62/lx9pcjbs1zbd83zg6twwym2r0000gn/T/go-build3212087168/b001/exe/test:
|
||||
-debug
|
||||
turns on debug mode, extra logging
|
||||
```
|
||||
|
||||
Note: any recognized flags need to come before any of the position arguments.
|
||||
The `debug` flag won't be picked up if we run the program like this:
|
||||
|
||||
```bash
|
||||
$ go run . 123 --debug
|
||||
```
|
||||
|
||||
[source](https://pkg.go.dev/flag)
|
||||
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
|
||||
```
|
||||
52
go/sort-slice-in-ascending-or-descending-order.md
Normal file
52
go/sort-slice-in-ascending-or-descending-order.md
Normal file
@@ -0,0 +1,52 @@
|
||||
# Sort Slice In Ascending Or Descending Order
|
||||
|
||||
The [`slices.Sort`](https://pkg.go.dev/slices#Sort) function defaults to
|
||||
sorting a slice in ascending order. If we want to control the sort order, we
|
||||
have to do a little more work. We can reach for the
|
||||
[`slices.SortFunc`](https://pkg.go.dev/slices#SortFunc) function. This allows
|
||||
us to define a sort function and in that function we can control whether the
|
||||
sort order is ascending or descending.
|
||||
|
||||
Here I've defined `SortItems` which takes a list of items constrained by the
|
||||
[`cmp.Ordered`](https://pkg.go.dev/cmp#Ordered) interface (so things like
|
||||
`int`, `string`, `uint64`, etc.). It takes a direction (`ASC` or `DESC`) as a
|
||||
second argument. It does the directional sort based on that second argument.
|
||||
|
||||
```go
|
||||
import (
|
||||
"cmp"
|
||||
"fmt"
|
||||
"slices"
|
||||
)
|
||||
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
ASC Direction = iota
|
||||
DESC
|
||||
)
|
||||
|
||||
func SortItems[T cmp.Ordered](items []T, dir Direction) {
|
||||
slices.SortFunc(items, func(i, j T) int {
|
||||
if dir == ASC {
|
||||
return cmp.Compare(i, j)
|
||||
} else if dir == DESC {
|
||||
return cmp.Compare(j, i)
|
||||
} else {
|
||||
panic(fmt.Sprintf("Unrecognized sort direction: %d", dir))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// items := []int{3,2,8,1}
|
||||
// SortItems(items, ASC)
|
||||
// // items => [1,2,3,8]
|
||||
// SortItems(items, DESC)
|
||||
// // items => [8,3,2,1]
|
||||
```
|
||||
|
||||
Because `slices.SortFunc` expects a negative value, zero, or positive value to
|
||||
determine the sort order, we use
|
||||
[`cmp.Compare`](https://pkg.go.dev/cmp#Compare) which returns those kinds of
|
||||
values. For ascending, we compare `i` to `j`. For descending, we swap them,
|
||||
comparing `j` to `i` to get the reverse sort order.
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user