mirror of
https://github.com/jbranchaud/til
synced 2026-01-05 16:18:01 +00:00
Compare commits
25 Commits
5f11b1665b
...
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 |
24
README.md
24
README.md
@@ -8,9 +8,9 @@ warrant a full blog post. These are things I've picked up by [Learning In
|
||||
Public™](https://dev.to/jbranchaud/how-i-built-a-learning-machine-45k9) and
|
||||
pairing with smart people at Hashrocket.
|
||||
|
||||
For a steady stream of TILs, [sign up for my newsletter](https://crafty-builder-6996.ck.page/e169c61186).
|
||||
For a steady stream of TILs, [sign up for my newsletter](https://visualmode.kit.com/newsletter).
|
||||
|
||||
_1695 TILs and counting..._
|
||||
_1715 TILs and counting..._
|
||||
|
||||
See some of the other learning resources I work on:
|
||||
|
||||
@@ -157,6 +157,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
|
||||
### Claude Code
|
||||
|
||||
- [Monitor Usage Limits From CLI](claude-code/monitor-usage-limits-from-cli.md)
|
||||
- [Open Current Prompt In Default Editor](claude-code/open-current-prompt-in-default-editor.md)
|
||||
|
||||
### Clojure
|
||||
@@ -380,6 +381,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Last Commit A File Appeared In](git/last-commit-a-file-appeared-in.md)
|
||||
- [List All Files Added During Span Of Time](git/list-all-files-added-during-span-of-time.md)
|
||||
- [List All Files Changed Between Two Branches](git/list-all-files-changed-between-two-branches.md)
|
||||
- [List All Git Aliases From gitconfig](git/list-all-git-aliases-from-gitconfig.md)
|
||||
- [List Branches That Contain A Commit](git/list-branches-that-contain-a-commit.md)
|
||||
- [List Commits On A Branch](git/list-commits-on-a-branch.md)
|
||||
- [List Different Commits Between Two Branches](git/list-different-commits-between-two-branches.md)
|
||||
@@ -446,6 +448,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
|
||||
### GitHub
|
||||
|
||||
- [Access Your GitHub Profile Photo](github/access-your-github-profile-photo.md)
|
||||
- [Open A PR To An Unforked Repo](github/open-a-pr-to-an-unforked-repo.md)
|
||||
- [Target Another Repo When Creating A PR](github/target-another-repo-when-creating-a-pr.md)
|
||||
- [Tell gh What The Default Repo Is](github/tell-gh-what-the-default-repo-is.md)
|
||||
@@ -503,6 +506,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Open Dashboard For Specific Add-On](heroku/open-dashboard-for-specific-add-on.md)
|
||||
- [Run SQL Against Remote Postgres Database](heroku/run-sql-against-remote-postgres-database.md)
|
||||
- [Set And Show Heroku Env Variables](heroku/set-and-show-heroku-env-variables.md)
|
||||
- [Specify Default Team And App For Project](heroku/specify-default-team-and-app-for-project.md)
|
||||
- [SSH Into Heroku Server Hosting App](heroku/ssh-into-heroku-server-hosting-app.md)
|
||||
|
||||
### HTML
|
||||
@@ -661,6 +665,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
### jj
|
||||
|
||||
- [Colocate jj And git Directories For Project](jj/colocate-jj-and-git-directories-for-project.md)
|
||||
- [Describe Current Changes And Create New Change](jj/describe-current-changes-and-create-new-change.md)
|
||||
- [Find System-wide Config File For User](jj/find-system-wide-config-file-for-user.md)
|
||||
- [Squash Changes Into Parent Commit Interactively](jj/squash-changes-into-parent-commit-interactively.md)
|
||||
|
||||
@@ -762,6 +767,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Doing Date Math](mysql/doing-date-math.md)
|
||||
- [Dump A Database To A File](mysql/dump-a-database-to-a-file.md)
|
||||
- [Echo A Message From A SQL File](mysql/echo-a-message-from-a-sql-file.md)
|
||||
- [Get Idea Of What Is In A JSON Column](mysql/get-idea-of-what-is-in-a-json-column.md)
|
||||
- [Ignore Duplicates When Inserting Records](mysql/ignore-duplicates-when-inserting-records.md)
|
||||
- [List Databases And Tables](mysql/list-databases-and-tables.md)
|
||||
- [Run Statements In A Transaction](mysql/run-statements-in-a-transaction.md)
|
||||
@@ -1161,6 +1167,8 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Rounding Numbers With Precision](rails/rounding-numbers-with-precision.md)
|
||||
- [Run A Rake Task Programmatically](rails/run-a-rake-task-programmatically.md)
|
||||
- [Run Commands With Specific Rails Version](rails/run-commands-with-specific-rails-version.md)
|
||||
- [Run Dev Processes With Overmind Instead Of Foreman](rails/run-dev-processes-with-overmind-instead-of-foreman.md)
|
||||
- [Run Rails Console With Remote Dokku App](rails/run-rails-console-with-remote-dokku-app.md)
|
||||
- [Run Some Code Whenever Rails Console Starts](rails/run-some-code-whenever-rails-console-starts.md)
|
||||
- [Scaffold Auth Functionality With Rails 8 Generator](rails/scaffold-auth-functionality-with-rails-8-generator.md)
|
||||
- [Schedule Sidekiq Jobs Out Into The Future](rails/schedule-sidekiq-jobs-out-into-the-future.md)
|
||||
@@ -1193,6 +1201,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Update Column Versus Update Attribute](rails/update-column-versus-update-attribute.md)
|
||||
- [Upgrading Your Manifest For Sprocket's 4](rails/upgrading-your-manifest-for-sprockets-4.md)
|
||||
- [Use IRB And Ruby Flags With Rails Console](rails/use-irb-and-ruby-flags-with-rails-console.md)
|
||||
- [Use .ruby Extension For Template File](rails/use-ruby-extension-for-template-file.md)
|
||||
- [Useful ActiveSupport Constants For Durations](rails/useful-active-support-constants-for-durations.md)
|
||||
- [Validate Column Data With Check Constraints](rails/validate-column-data-with-check-constraints.md)
|
||||
- [Verify And Read A Signed Cookie Value](rails/verify-and-read-a-signed-cookie-value.md)
|
||||
@@ -1354,6 +1363,8 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Create a CSV::Table Object](ruby/create-a-csv-table-object.md)
|
||||
- [Create A Hash From An Array Of Arrays](ruby/create-a-hash-from-an-array-of-arrays.md)
|
||||
- [Create Listing Of All Middleman Pages](ruby/create-listing-of-all-middleman-pages.md)
|
||||
- [Create Mock Class That Can Be Overridden](ruby/create-mock-class-that-can-be-overridden.md)
|
||||
- [Create A Module Of Utility Functions](ruby/create-a-module-of-utility-functions.md)
|
||||
- [Create Named Structs With Struct.new](ruby/create-named-structs-with-struct-new.md)
|
||||
- [Create Thumbnail Image For A PDF](ruby/create-thumbnail-image-for-a-pdf.md)
|
||||
- [Decompose Unicode Character With Diacritic Mark](ruby/decompose-unicode-character-with-diacritic-mark.md)
|
||||
@@ -1397,15 +1408,18 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Iterate With An Offset Index](ruby/iterate-with-an-offset-index.md)
|
||||
- [Include Extra Context In A Honeybadger Notify](ruby/include-extra-context-in-a-honeybadger-notify.md)
|
||||
- [Ins And Outs Of Pry](ruby/ins-and-outs-of-pry.md)
|
||||
- [Install And Require Gems Inline Without Gemfile](ruby/install-and-require-gems-inline-without-gemfile.md)
|
||||
- [Install Latest Version Of Ruby With asdf](ruby/install-latest-version-of-ruby-with-asdf.md)
|
||||
- [Invoking Rake Tasks Multiple Times](ruby/invoking-rake-tasks-multiple-times.md)
|
||||
- [IRB Has Built-In Benchmarking With Ruby 3](ruby/irb-has-built-in-benchmarking-with-ruby-3.md)
|
||||
- [Join URI Path Parts](ruby/join-uri-path-parts.md)
|
||||
- [Jump Out Of A Nested Context With Throw/Catch](ruby/jump-out-of-a-nested-context-with-throw-catch.md)
|
||||
- [Last Raised Exception In The Call Stack](ruby/last-raised-exception-in-the-call-stack.md)
|
||||
- [Limit Split](ruby/limit-split.md)
|
||||
- [List The Running Ruby Version](ruby/list-the-running-ruby-version.md)
|
||||
- [Listing Local Variables](ruby/listing-local-variables.md)
|
||||
- [Make An Executable Ruby Script](ruby/make-an-executable-ruby-script.md)
|
||||
- [Make Structs Easier To Use With Keyword Initialization](ruby/make-structs-easier-to-use-with-keyword-initialization.md)
|
||||
- [Map With Index Over An Array](ruby/map-with-index-over-an-array.md)
|
||||
- [Mock Method Chain Calls With RSpec](ruby/mock-method-chain-calls-with-rspec.md)
|
||||
- [Mocking Requests With Partial URIs Using Regex](ruby/mocking-requests-with-partial-uris-using-regex.md)
|
||||
@@ -1433,6 +1447,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Read The First Line From A File](ruby/read-the-first-line-from-a-file.md)
|
||||
- [Refer To Implicit Block Argument With It](ruby/refer-to-implicit-block-argument-with-it.md)
|
||||
- [Reference Hash Key With Safe Navigation](ruby/reference-hash-key-with-safe-navigation.md)
|
||||
- [Regenerate Lock File With Newer Bundler](ruby/regenerate-lock-file-with-newer-bundler.md)
|
||||
- [Rendering ERB](ruby/rendering-erb.md)
|
||||
- [Replace The Current Process With An External Command](ruby/replace-the-current-process-with-an-external-command.md)
|
||||
- [Require Entire Gemfile In Pry Session](ruby/require-entire-gemfile-in-pry-session.md)
|
||||
@@ -1492,6 +1507,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [OSX sed Does Regex A Bit Different](sed/osx-sed-does-regex-a-bit-different.md)
|
||||
- [Output Only Lines Involved In A Substitution](sed/output-only-lines-involved-in-a-substitution.md)
|
||||
- [Reference A Capture In The Regex](sed/reference-a-capture-in-the-regex.md)
|
||||
- [Reference The Full Match In The Replacement](sed/reference-the-full-match-in-the-replacement.md)
|
||||
- [Use An Alternative Delimiter In A Substitution](sed/use-an-alternative-delimiter-in-a-substitution.md)
|
||||
|
||||
### Shell
|
||||
@@ -1518,6 +1534,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
|
||||
### Taskfile
|
||||
|
||||
- [Create Interactive Picker For Set Of Subtasks](taskfile/create-interactive-picker-for-set-of-subtasks.md)
|
||||
- [Run A Task If It Meets Criteria](taskfile/run-a-task-if-it-meets-criteria.md)
|
||||
|
||||
### tmux
|
||||
@@ -1553,6 +1570,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Reset An Option Back To Its Default Value](tmux/reset-an-option-back-to-its-default-value.md)
|
||||
- [Set Environment Variables When Creating Session](tmux/set-environment-variables-when-creating-session.md)
|
||||
- [Set Session Specific Environment Variables](tmux/set-session-specific-environment-variables.md)
|
||||
- [Set Up Forwarding Prefix For Nested Session](tmux/set-up-forwarding-prefix-for-nested-session.md)
|
||||
- [Show The Current Value For An Option](tmux/show-the-current-value-for-an-option.md)
|
||||
- [Swap Split Panes](tmux/swap-split-panes.md)
|
||||
- [Switch To A Specific Session And Window](tmux/switch-to-a-specific-session-and-window.md)
|
||||
@@ -1615,6 +1633,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Curl With Cookies](unix/curl-with-cookies.md)
|
||||
- [Curling For Headers](unix/curling-for-headers.md)
|
||||
- [Curling With Basic Auth Credentials](unix/curling-with-basic-auth-credentials.md)
|
||||
- [Determine ipv4 And ipv6 Public IP Addresses](unix/determine-ipv4-and-ipv6-public-ip-addresses.md)
|
||||
- [Different Ways To Generate A v4 UUID](unix/different-ways-to-generate-a-v4-uuid.md)
|
||||
- [Display All The Terminal Colors](unix/display-all-the-terminal-colors.md)
|
||||
- [Display Free Disk Space](unix/display-free-disk-space.md)
|
||||
@@ -1708,6 +1727,7 @@ If you've learned something here, support my efforts writing daily TILs by
|
||||
- [Produce A Lowercase V4 UUID](unix/produce-a-lowercase-v4-uuid.md)
|
||||
- [Provide A Fallback Value For Unset Parameter](unix/provide-a-fallback-value-for-unset-parameter.md)
|
||||
- [Remove A Directory Called `-p`](unix/remove-a-directory-called-dash-p.md)
|
||||
- [Rename A Bunch Of Files By Constructing mv Commands](unix/rename-a-bunch-of-files-by-constructing-mv-commands.md)
|
||||
- [Repeat Yourself](unix/repeat-yourself.md)
|
||||
- [Replace Pattern Across Many Files In A Project](unix/replace-pattern-across-many-files-in-a-project.md)
|
||||
- [Run A Command Repeatedly Several Times](unix/run-a-command-repeatedly-several-times.md)
|
||||
|
||||
@@ -49,6 +49,7 @@ tasks:
|
||||
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
|
||||
|
||||
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.
|
||||
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.
|
||||
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)
|
||||
34
heroku/specify-default-team-and-app-for-project.md
Normal file
34
heroku/specify-default-team-and-app-for-project.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Specify Default Team And App For Project
|
||||
|
||||
Typically when you run commands with the Heroku CLI you'll need to specify the
|
||||
name of the app on Heroku you're targeting with the `--app` flag. However, to
|
||||
first see the names of the apps you may want to run `heroku apps` (or `heroku
|
||||
list`). That will list the apps for your default team.
|
||||
|
||||
If you need to see apps for a different team (i.e. organization), you'll need to
|
||||
specify that team either with the `--team` flag or by setting that as an
|
||||
environment variable.
|
||||
|
||||
Here I do the latter in an `.envrc` file:
|
||||
|
||||
```
|
||||
# Heroku
|
||||
export HEROKU_ORGANIZATION=visualmode
|
||||
```
|
||||
|
||||
Once that is set and the environment reloaded, running `heroku apps` will show
|
||||
the apps specific to that team on Heroku.
|
||||
|
||||
Similarly, if you want to set a default app for your project so that you don't
|
||||
have to always specify the `--app` flag, you can update your `.envrc`
|
||||
accordingly.
|
||||
|
||||
```
|
||||
# Heroku
|
||||
export HEROKU_ORGANIZATION=visualmode
|
||||
export HEROKU_APP=my-app
|
||||
```
|
||||
|
||||
I had a hard time finding official documentation for this which is why I'm
|
||||
writing this up here. I've manually verified this works with my own team and
|
||||
app.
|
||||
28
jj/describe-current-changes-and-create-new-change.md
Normal file
28
jj/describe-current-changes-and-create-new-change.md
Normal file
@@ -0,0 +1,28 @@
|
||||
# Describe Current Changes And Create New Change
|
||||
|
||||
One of the first patterns I learned with `jj` was a pair of commands to
|
||||
essentially "commit" the working copy and start a fresh, new change. So if I am
|
||||
done making some changes, I can add a description to the `(no description)`
|
||||
working copy and then start a new working copy _change_.
|
||||
|
||||
```bash
|
||||
$ jj describe -m "Add status subcommand to show current status"
|
||||
$ jj new
|
||||
```
|
||||
|
||||
I learned from [Steve](https://steveklabnik.com/) in the [jj
|
||||
discord](https://discord.gg/dkmfj3aGQN) that a shorthand for this pattern is to
|
||||
use the `jj commit` command directly.
|
||||
|
||||
> When called without path arguments or `--interactive`, `jj commit` is
|
||||
> equivalent to `jj describe` followed by `jj new`.
|
||||
|
||||
That means, instead of the above pair of commands, I could have done:
|
||||
|
||||
```bash
|
||||
$ jj commit -m "Add status subcommand to show current status"
|
||||
```
|
||||
|
||||
That would have had the same result in my case. However, notice the caveats
|
||||
mentioned in the quote above and check out `man jj-commit` for more details on
|
||||
that.
|
||||
40
mysql/get-idea-of-what-is-in-a-json-column.md
Normal file
40
mysql/get-idea-of-what-is-in-a-json-column.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Get Idea Of What Is In A JSON Column
|
||||
|
||||
While digging through some data trying to reacquaint myself with the overall
|
||||
schema and data model, I ran into an issue selecting rows from this
|
||||
`content_resource` table. There was so much text packed in to the `"body"`,
|
||||
`"summary"`, and `"description"` key-value pairs of `fields` JSON column that a
|
||||
simple `select * ... limit 3;` was overwhelming the screen with text and table
|
||||
formatting characters (i.e. `+------+-------`).
|
||||
|
||||
I figured the `fields` JSON followed a reliable structure, at least for records
|
||||
of the same `type`. So, let's start by only grabbing the
|
||||
[`json_keys`](https://dev.mysql.com/doc/refman/8.4/en/json-search-functions.html#function_json-keys)
|
||||
so that I can get a sense of the shape of the JSON.
|
||||
|
||||
```sql
|
||||
select id, json_keys(fields)
|
||||
from content_resource
|
||||
where type = 'post'
|
||||
limit 3;
|
||||
|
||||
+-----+-----------------------------------------------------------------------------------------------------------+
|
||||
| id | json_keys(`fields`) |
|
||||
+-----+-----------------------------------------------------------------------------------------------------------+
|
||||
| 1 | ["body", "slug", "state", "title", "summary", "postType", "visibility", "description", "originalLessonId"] |
|
||||
| 2 | ["body", "slug", "state", "title", "summary", "postType", "visibility", "description", "originalLessonId"] |
|
||||
| 3 | ["body", "slug", "state", "title", "summary", "postType", "visibility", "description", "originalLessonId"] |
|
||||
+-----+-----------------------------------------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
For the `post` type, I see the same keys for this sampling of rows. Now I have
|
||||
an idea what keys are present and can start digging in further.
|
||||
|
||||
My next query might look something like this:
|
||||
|
||||
```sql
|
||||
select id, fields->'$.slug', fields->'$.title', fields->'$.state'
|
||||
from content_resource
|
||||
where type = 'post'
|
||||
limit 3;
|
||||
```
|
||||
46
rails/run-dev-processes-with-overmind-instead-of-foreman.md
Normal file
46
rails/run-dev-processes-with-overmind-instead-of-foreman.md
Normal file
@@ -0,0 +1,46 @@
|
||||
# Run Dev Processes With Overmind Instead Of Foreman
|
||||
|
||||
Most Rails projects that I have worked on have used
|
||||
[`foreman`](https://github.com/ddollar/foreman) as a development dependency for
|
||||
running all the processes declared in your Procfile (`Procfile.dev`). As far as
|
||||
having a single command to run everything (Rails server, asset building,
|
||||
worker(s), etc.), it does the job.
|
||||
|
||||
`foreman` has some serious points of friction though. The one that really stands
|
||||
out to me is that when I try to debug the development Rails server with
|
||||
`binding.irb` or `binding.pry`, the other processes tend to interfere.
|
||||
|
||||
The alternative to `foreman` that I've been trying out recently is
|
||||
[`overmind`](https://github.com/DarthSim/overmind). A specific selling point of
|
||||
`overmind` is that it runs all the development processes in a `tmux` session.
|
||||
That means you can individually connect to, inspect, and restart each process.
|
||||
|
||||
Once you've installed `overmind` (`brew install overmind`), then you can easily
|
||||
swap it in for `foreman` like so:
|
||||
|
||||
```bash
|
||||
$ overmind start -f Procfile.dev
|
||||
```
|
||||
|
||||
You can connect to any of those processes directly:
|
||||
|
||||
```bash
|
||||
$ overmind connect sidekiq
|
||||
```
|
||||
|
||||
When you want to `binding.irb` the Rails server, you can specifically connect to
|
||||
the `web` process to do that.
|
||||
|
||||
```bash
|
||||
$ overmind connect web
|
||||
```
|
||||
|
||||
If you need to stop all the process, you can run the `kill` subcommand.
|
||||
|
||||
```bash
|
||||
$ overmind kill
|
||||
```
|
||||
|
||||
Lastly, if you have a `bin/dev` script in your project, it is probably using
|
||||
`foreman`. If you and your team prefer `overmind`, then update that script
|
||||
accordingly and you can simply run `bin/dev` going forward.
|
||||
49
rails/run-rails-console-with-remote-dokku-app.md
Normal file
49
rails/run-rails-console-with-remote-dokku-app.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Run Rails Console With Remote Dokku App
|
||||
|
||||
Whenever I want to `rails console` into the _staging_ server of an app I'm
|
||||
working on, I first have to `ssh` into server and then I have to come up with
|
||||
the [`dokku`](https://dokku.com/) command to run `rails console` against the app
|
||||
on that server.
|
||||
|
||||
```bash
|
||||
local> ssh app-staging # app-staging is an SSH alias
|
||||
staging> dokku run my-app rails console
|
||||
```
|
||||
|
||||
I figured out how to reduce the friction of this by collapsing it into a single
|
||||
command that I can run locally. I can remotely run the `dokku` command with
|
||||
`ssh` using an interactive session (`-t`).
|
||||
|
||||
```bash
|
||||
local> ssh -t app-staging dokku run my-app rails console
|
||||
```
|
||||
|
||||
That will open up a `rails console` session directly in the current shell
|
||||
session via a remote SSH connection. The `-t` flag is important because that
|
||||
makes the session interactive so that I can interact with the REPL.
|
||||
|
||||
I've even packaged this up into a bin script (`bin/staging-console`) with a
|
||||
couple checks to enhance the DX. I won't put the whole thing here, but the gist
|
||||
of it is:
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
|
||||
if [ -z "$DOKKU_STAGING_SSH_ALIAS" ]; then
|
||||
echo "Error: DOKKU_STAGING_SSH_ALIAS environment variable is not set."
|
||||
echo ""
|
||||
# echo more help details here ...
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if SSH alias exists
|
||||
# ...
|
||||
|
||||
# Check if we can reach the server
|
||||
# ...
|
||||
|
||||
# Run the console
|
||||
ssh -t "$DOKKU_STAGING_SSH_ALIAS" dokku run my-app rails console "$@"
|
||||
```
|
||||
42
rails/use-ruby-extension-for-template-file.md
Normal file
42
rails/use-ruby-extension-for-template-file.md
Normal file
@@ -0,0 +1,42 @@
|
||||
# Use .ruby Extension For Template File
|
||||
|
||||
An interesting feature of Rails that I can't seem to find documented anywhere is
|
||||
that you can write a template file with plain Ruby by using the `.ruby`
|
||||
extension. For instance, you might want to render some JSON from a template.
|
||||
Instead of using `jbuilder` or `erb`, you can have a `show.json.ruby` file. This
|
||||
is also popular with Turbo Stream files -- e.g. `update.turbo_stream.ruby`.
|
||||
|
||||
How this works is that the entire file is evaluated as if it were a `.rb` file.
|
||||
Then the return value of the final statement is what is returned and rendered by
|
||||
Rails.
|
||||
|
||||
```ruby
|
||||
author_byline = @book.authors.map(&:name).to_sentence
|
||||
|
||||
data = {
|
||||
id: @book.id,
|
||||
title: @book.title,
|
||||
author: author_byline,
|
||||
status: @book.published_at > Time.current ? 'Coming Soon' : 'Published',
|
||||
publication_year: @book.published_at.year
|
||||
}
|
||||
|
||||
data.to_json
|
||||
```
|
||||
|
||||
That final line converts the hash of data that we've built up into a JSON string
|
||||
that can then be rendered by the controller action that corresponds to this view
|
||||
template.
|
||||
|
||||
Similarly, you can have a Turbo Stream template `show.turbo_stream.ruby` that
|
||||
looks something like this:
|
||||
|
||||
```ruby
|
||||
[
|
||||
turbo_stream.prepend("posts", @post),
|
||||
turbo_stream.update("form", partial: "form", locals: { post: Post.new })
|
||||
].join
|
||||
```
|
||||
|
||||
This template file is made up of a single statement which is an array of turbo
|
||||
stream results that get joined together.
|
||||
59
ruby/create-a-module-of-utility-functions.md
Normal file
59
ruby/create-a-module-of-utility-functions.md
Normal file
@@ -0,0 +1,59 @@
|
||||
# Create A Module Of Utility Functions
|
||||
|
||||
In my [latest blog post](https://www.visualmode.dev/create-a-module-of-utility-functions-in-ruby),
|
||||
I went into full detail about how the [`Module#module_function` method](https://ruby-doc.org/3.4.1/Module.html#method-i-module_function) works.
|
||||
It creates both a module of utility functions that we can access directly on
|
||||
that module like we would with `self` methods. It can also be included in a
|
||||
class as a way of sharing copies of those utility functions with the class. A
|
||||
key point to them being copies is that they can then be overridden by the
|
||||
including class.
|
||||
|
||||
Here is the example I used in the blog post:
|
||||
|
||||
```ruby
|
||||
module MarkdownHelpers
|
||||
module_function
|
||||
|
||||
def heading(text, level = 1)
|
||||
("#" * level) + " #{text}"
|
||||
end
|
||||
|
||||
def link(text, href)
|
||||
"[#{text}](#{href})"
|
||||
end
|
||||
|
||||
def image(alt_text, href)
|
||||
"!#{link(alt_text, href)}"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
I won't cover everything that the blog post covers, but what I found really nice
|
||||
about this pattern is that I can call those utility functions directly with the
|
||||
module as the receiver:
|
||||
|
||||
```bash
|
||||
$ ruby -r ./markdown_helpers.rb -e 'puts MarkdownHelpers.link("Click here", "https://example.com")'
|
||||
[Click here](https://example.com)
|
||||
```
|
||||
|
||||
The alternative to this generally looks like:
|
||||
|
||||
```ruby
|
||||
module MarkdownHelpers
|
||||
def self.heading(text, level = 1)
|
||||
("#" * level) + " #{text}"
|
||||
end
|
||||
|
||||
def self.link(text, href)
|
||||
"[#{text}](#{href})"
|
||||
end
|
||||
|
||||
def self.image(alt_text, href)
|
||||
"!#{link(alt_text, href)}"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
That would be fine, but we completely lose out on the ability to include it as a
|
||||
mix-in with other classes.
|
||||
85
ruby/create-mock-class-that-can-be-overridden.md
Normal file
85
ruby/create-mock-class-that-can-be-overridden.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# Create Mock Class That Can Be Overridden
|
||||
|
||||
Let's say I've defined a `MockTwilioClient` class for my system tests so that
|
||||
they don't have to actually make calls out to the Twilio API.
|
||||
|
||||
```ruby
|
||||
class MockTwilioClient
|
||||
def send_sms_message
|
||||
"MSG_SID_123"
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Now, any test that is exercising behavior that uses those parts of the
|
||||
`TwilioClient` can mock those calls in a predictable way.
|
||||
|
||||
The above class always is successful when `send_sms_message` is called. What if
|
||||
we want to simulate an error response? We need a way to override the client for
|
||||
specific testing scenarios.
|
||||
|
||||
Let's create a helper that can create `MockTwilioClient` instances, either as is
|
||||
or with overrides.
|
||||
|
||||
```ruby
|
||||
def create_mock_twilio_client(&block)
|
||||
if block_given?
|
||||
Class.new(MockTwilioClient, &block).new
|
||||
else
|
||||
MockTwilioClient.new
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
If we pass it a block with specific method overrides, it will create a one-off
|
||||
anonymous subclass of `MockTwilioClient` with that block which effectively
|
||||
overrides the parent class's methods.
|
||||
|
||||
We can put this to use like so:
|
||||
|
||||
```ruby
|
||||
require 'test_helper'
|
||||
|
||||
class SomeSystemTest < SystemTestCase
|
||||
class MockTwilioClient
|
||||
def send_sms_message
|
||||
"MSG_SID_123"
|
||||
end
|
||||
end
|
||||
|
||||
def create_mock_twilio_client(&block)
|
||||
if block_given?
|
||||
Class.new(MockTwilioClient, &block).new
|
||||
else
|
||||
MockTwilioClient.new
|
||||
end
|
||||
end
|
||||
|
||||
test "send message to customer" do
|
||||
mock_client = create_mock_twilio_client
|
||||
|
||||
TwilioClient.stub(:new, mock_client) do
|
||||
# some action that uses `send_sms_message`
|
||||
|
||||
# some assertions ...
|
||||
end
|
||||
end
|
||||
|
||||
test "fail to send message to customer" do
|
||||
mock_client = create_mock_twilio_client do
|
||||
def send_sms_message
|
||||
raise "Failed to send message"
|
||||
end
|
||||
end
|
||||
|
||||
TwilioClient.stub(:new, mock_client) do
|
||||
# some action that uses `send_sms_message`
|
||||
|
||||
# some assertions ...
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
In the second test case, I override the _success path_ with a version of
|
||||
`send_sms_message` that raises an error.
|
||||
74
ruby/install-and-require-gems-inline-without-gemfile.md
Normal file
74
ruby/install-and-require-gems-inline-without-gemfile.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Install And Require Gems Inline Without Gemfile
|
||||
|
||||
[Bundler](https://bundler.io/) has an _inline_ feature where you can declare
|
||||
gems that should be installed and required for the current file without the use
|
||||
of a `Gemfile`. This is useful for creating a single-file Ruby script that can
|
||||
define its own dependencies.
|
||||
|
||||
Require `"bundler/inline"` and then add a `gemfile` block toward the top of the
|
||||
script to specify the source and any gems.
|
||||
|
||||
```ruby
|
||||
require "bundler/inline"
|
||||
|
||||
gemfile do
|
||||
source "https://rubygems.org"
|
||||
gem "httparty"
|
||||
end
|
||||
```
|
||||
|
||||
When the script gets run (e.g. `ruby script.rb`), it will install the gems (if
|
||||
they haven't already been installed) and then run the script. You can specify
|
||||
version constraints just like you'd do in a `Gemfile`.
|
||||
|
||||
Here is a single-file script using this approach that I wrote to interact with
|
||||
the Kit API.
|
||||
|
||||
```ruby
|
||||
#!/usr/bin/env ruby
|
||||
require "bundler/inline"
|
||||
|
||||
gemfile do
|
||||
source "https://rubygems.org"
|
||||
gem "httparty"
|
||||
end
|
||||
|
||||
require "json"
|
||||
require_relative "kit_client"
|
||||
|
||||
API_SECRET = ENV["KIT_API_SECRET"]
|
||||
|
||||
def fetch_all_tags(api_secret)
|
||||
client = KitClient.new("https://api.kit.com/v4", api_secret)
|
||||
|
||||
tags = []
|
||||
after_cursor = nil
|
||||
|
||||
loop do
|
||||
params = {per_page: 1000}
|
||||
params[:after] = after_cursor if after_cursor
|
||||
|
||||
response = client.get("/tags", params)
|
||||
|
||||
data = JSON.parse(response.body)
|
||||
tags.concat(data["tags"])
|
||||
|
||||
break unless data["pagination"]["has_next_page"]
|
||||
after_cursor = data["pagination"]["end_cursor"]
|
||||
end
|
||||
|
||||
tags
|
||||
end
|
||||
|
||||
tags = fetch_all_tags(API_SECRET)
|
||||
|
||||
tags.each do |tag|
|
||||
puts tag
|
||||
end
|
||||
```
|
||||
|
||||
Because I've specified the shebang at the top of the file (and assuming I've
|
||||
`chmod +x fetch_tags.rb`), I can run this directly as a script with
|
||||
`./fetch_tags.rb`.
|
||||
|
||||
[source](https://bundler.io/guides/bundler_in_a_single_file_ruby_script.html)
|
||||
43
ruby/join-uri-path-parts.md
Normal file
43
ruby/join-uri-path-parts.md
Normal file
@@ -0,0 +1,43 @@
|
||||
# Join URI Path Parts
|
||||
|
||||
The
|
||||
[`URI.join`](https://ruby-doc.org/stdlib-2.5.1/libdoc/uri/rdoc/URI.html#method-c-join)
|
||||
method seems like a handy way to combine a base URL with some subpath. However,
|
||||
there are some subtle gotchas depending on where forward slashes appear in the
|
||||
two arguments.
|
||||
|
||||
Let's first look at the, in my opinion, desired behavior:
|
||||
|
||||
```ruby
|
||||
> URI.join("https://example.com/api/v1/", "users")
|
||||
=> #<URI::HTTPS https://example.com/api/v1/users>
|
||||
```
|
||||
|
||||
The base URL has a trailing slash and the path that I want to join to it has no
|
||||
leading slash. The result is a path where `users` is joined to the end of the
|
||||
base URL. That's what I'm looking for.
|
||||
|
||||
Now, let's see some variations on the above approach that give results that I
|
||||
wasn't expecting and don't want.
|
||||
|
||||
```ruby
|
||||
> URI.join("https://example.com/api/v1", "/users") # 1
|
||||
=> #<URI::HTTPS https://example.com/users>
|
||||
> URI.join("https://example.com/api/v1", "users") # 2
|
||||
=> #<URI::HTTPS https://example.com/api/users>
|
||||
> URI.join("https://example.com/api/v1/", "/users") # 3
|
||||
=> #<URI::HTTPS https://example.com/users>
|
||||
```
|
||||
|
||||
1. No trailing slash on the base URL. Leading slash on the path to join. The
|
||||
path portion of the base URL is wiped out and `/users` is joined in.
|
||||
2. No trailing slash on the base URL. No leading slash on the path to join. The
|
||||
`users` path replaces the last part of the path in the base URL.
|
||||
3. Both a trailing slash in the base URL and a leading slash in the path to
|
||||
join. Same behavior as 1.
|
||||
|
||||
I have two takeaways from this:
|
||||
- Use with caution. If I'm going to use `URI.join` for this purpose, I need to
|
||||
be careful to only use the form in the first code block.
|
||||
- The `URI.join` method is probably meant to be primarily used to join a domain
|
||||
(e.g. `http://example.com`) that has no path with some path segment.
|
||||
@@ -0,0 +1,44 @@
|
||||
# Make Structs Easier To Use With Keyword Initialization
|
||||
|
||||
Typically a [`Struct`](https://ruby-doc.org/3.4.1/Struct.html#method-c-new) in
|
||||
Ruby is defined and initialized like so:
|
||||
|
||||
```ruby
|
||||
> Subscriber = Struct.new(:email, :first_name, :status, :tags)
|
||||
=> Subscriber
|
||||
> s1 = Subscriber.new('bob.burgers@example.com', 'Bob', :active, [:food, :family])
|
||||
=> #<struct Subscriber email="bob.burgers@example.com", first_name="Bob", status=:active, tags=[:food, :family]>
|
||||
> s1.email
|
||||
=> "bob.burgers@example.com"
|
||||
```
|
||||
|
||||
That's a nice way to structure light-weight objects.
|
||||
|
||||
A potential challenge with multi-argument `Struct` definitions like this,
|
||||
especially when they aren't colocated with initialization, is that it can be
|
||||
hard to remember or distinguish the argument order when initializing an instance
|
||||
of one.
|
||||
|
||||
Ruby 2.5 added the `keyword_init` option to help with this exact issue. When
|
||||
that option is set to `true` for a `Struct` definition, then we get to
|
||||
initialize it with keyword arguments rather than positional arguments.
|
||||
|
||||
```ruby
|
||||
> Subscriber = Struct.new(:email, :first_name, :status, :tags, keyword_init: true)
|
||||
=> Subscriber(keyword_init: true)
|
||||
* s1 = Subscriber.new(
|
||||
* first_name: 'Bob',
|
||||
* email: 'bob.burgers@example.com',
|
||||
* tags: [:food, :family],
|
||||
* status: :active
|
||||
> )
|
||||
=> #<struct Subscriber email="bob.burgers@example.com", first_name="Bob", status=:active, tags=[:food, :family]>
|
||||
> s1.email
|
||||
=> "bob.burgers@example.com"
|
||||
```
|
||||
|
||||
Notice I have to use keyword arguments now and that because of that I can
|
||||
organize them in whatever order makes sense. Coming back to view this line of
|
||||
code later, it is easy to see attribute each value corresponds to.
|
||||
|
||||
[source](https://www.bigbinary.com/blog/ruby-2-5-allows-creating-structs-with-keyword-arguments)
|
||||
34
ruby/regenerate-lock-file-with-newer-bundler.md
Normal file
34
ruby/regenerate-lock-file-with-newer-bundler.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Regenerate Lock File With Newer Bundler
|
||||
|
||||
While upgrading to the latest Ruby version (4.0.0), I also wanted to upgrade the
|
||||
version of `bundler` that my project uses. This shows up at the bottom of the
|
||||
`Gemfile.lock` file as the `BUNDLED WITH` line. Despite installing the latest
|
||||
version of `bundler`, I get the following message when I try to install
|
||||
dependencies.
|
||||
|
||||
```bash
|
||||
$ bundle install
|
||||
|
||||
Bundler 4.0.3 is running, but your lockfile was generated with 2.6.2.
|
||||
Installing Bundler 2.6.2 and restarting using that version.
|
||||
...
|
||||
```
|
||||
|
||||
Instead, what we need to tell `bundle` to update the locked version of `bundler`
|
||||
in the `Gemfile.lock`.
|
||||
|
||||
```bash
|
||||
$ bundle update --bundler
|
||||
|
||||
Fetching gem metadata from https://rubygems.org/.........
|
||||
Resolving dependencies...
|
||||
Bundle updated!
|
||||
```
|
||||
|
||||
The `--bundler` flag for `bundle-update` says the following:
|
||||
|
||||
> Update the locked version of bundler to the invoked bundler version.
|
||||
|
||||
So we could pass a specific `bundler` version to that flag, but in this case I
|
||||
want to use the version I'm invoking it with which is the latest that I just
|
||||
installed.
|
||||
27
sed/reference-the-full-match-in-the-replacement.md
Normal file
27
sed/reference-the-full-match-in-the-replacement.md
Normal file
@@ -0,0 +1,27 @@
|
||||
# Reference The Full Match In The Replacement
|
||||
|
||||
The `&` can be used in the replacement part of a `sed` expression as reference
|
||||
to the string match for this iteration of the expression. The occurrence of `&`
|
||||
will be replaced with that entire match.
|
||||
|
||||
As the `sed` man page puts it:
|
||||
|
||||
> An ampersand (“&”) appearing in the replacement is replaced by the string
|
||||
> matching the RE.
|
||||
|
||||
I made use of this recently with [a `sed` expression that was evaluating a list
|
||||
of filenames that I wanted to construct into a sequence of `mv`
|
||||
commands](unix/rename-a-bunch-of-files-by-constructing-mv-commands.md). I needed
|
||||
the filename that I was matching on to appear as the first argument of the `mv`
|
||||
command I was constructing.
|
||||
|
||||
Here is what that looks like:
|
||||
|
||||
```bash
|
||||
$ ls *.pdf |
|
||||
sed 's/\(..\)\(..\)\(..\) Statement\.pdf/mv "&" "20\3-\1-\2-statement.pdf"/'
|
||||
```
|
||||
|
||||
Notice right after `mv` in literal quotes is the `&`. That will be replaced in
|
||||
the resulting replacement with the full matching string of the regular
|
||||
expression in the first part of the sed statement (`s/<RE>/`).
|
||||
74
taskfile/create-interactive-picker-for-set-of-subtasks.md
Normal file
74
taskfile/create-interactive-picker-for-set-of-subtasks.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Create Interactive Picker For Set Of Subtasks
|
||||
|
||||
For [my TIL repo](https://github.com/jbranchaud/til), I have a `Taskfile.yml`
|
||||
that defines a set of `notes:*` tasks for interacting with a `NOTES.md` file
|
||||
that lives in a private Git submodule.
|
||||
|
||||
I wanted to make it easier on myself to not have to remember all the different
|
||||
`notes` subtasks, so I created a helper task to make it easy to see the options
|
||||
and run one.
|
||||
|
||||
A summary of the Taskfile is shown below including the entirety of the `notes`
|
||||
task. That task will parse a listing of the available tasks (via `task --list`
|
||||
and some `sed` commands) and pass those to `fzf` to provide an interactive
|
||||
picker of the available subtasks.
|
||||
|
||||
```yaml
|
||||
tasks:
|
||||
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:
|
||||
...
|
||||
|
||||
notes:sync:
|
||||
...
|
||||
|
||||
notes:open:
|
||||
...
|
||||
|
||||
notes:push:
|
||||
...
|
||||
|
||||
notes:status:
|
||||
...
|
||||
|
||||
notes:pull:
|
||||
...
|
||||
|
||||
notes:diff:
|
||||
...
|
||||
|
||||
notes:log:
|
||||
...
|
||||
```
|
||||
|
||||
Now I can run the `notes` task to get a summary and interactive picker that
|
||||
looks like the following:
|
||||
|
||||
```sh
|
||||
❯ task notes
|
||||
Select notes task:
|
||||
9/9
|
||||
> │ Interactive picker for notes tasks
|
||||
diff: Show uncommitted changes in notes
|
||||
edit: All-in-one edit, commit, and push notes
|
||||
log: Show recent commit history for notes
|
||||
open: Opens NOTES.md (syncs latest changes first) in default editor
|
||||
pull: Pull latest changes (alias for sync)
|
||||
push: Commit and push changes to notes submodule
|
||||
status: Check status of notes submodule
|
||||
sync: Sync latest changes from the notes submodule
|
||||
```
|
||||
|
||||
It pulls in the subtask name and description. I can then use `fzf`'s navigation
|
||||
and filtering to narrow down and select the task I want to run.
|
||||
24
tmux/set-up-forwarding-prefix-for-nested-session.md
Normal file
24
tmux/set-up-forwarding-prefix-for-nested-session.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# Set Up Forwarding Prefix For Nested Session
|
||||
|
||||
I use
|
||||
[`ctrl-z`](https://github.com/jbranchaud/dotfiles/blob/main/config/tmux/tmux.conf#L57)
|
||||
(instead of `ctrl-b`) for my tmux prefix key. I also have [`ctrl-z
|
||||
ctrl-z`](https://github.com/jbranchaud/dotfiles/blob/main/config/tmux/tmux.conf#L138-L139)
|
||||
configured to toggle back and forth between the previously visited window.
|
||||
|
||||
With that in mind, I needed to set up a specific keybinding to send the prefix
|
||||
key to an inner (nested) tmux session. That's because sometimes, like with a
|
||||
tool such as `overmind`, you can end up connected to a tmux session while
|
||||
already within a tmux session.
|
||||
|
||||
So, I have `Ctrl-z a` send the prefix key to the inner tmux session. This is
|
||||
what I added to my
|
||||
[`tmux.conf`](https://github.com/jbranchaud/dotfiles/blob/main/config/tmux/tmux.conf#L167-L168):
|
||||
|
||||
```
|
||||
# send prefix to inner tmux session (C-z a)
|
||||
bind-key a send-prefix
|
||||
```
|
||||
|
||||
Simply doing `Ctrl-z d` will detach me from the outer tmux session. If I want to
|
||||
detach from an inner tmux session, I can use my new keybinding -- `Ctrl-z a d`.
|
||||
29
unix/determine-ipv4-and-ipv6-public-ip-addresses.md
Normal file
29
unix/determine-ipv4-and-ipv6-public-ip-addresses.md
Normal file
@@ -0,0 +1,29 @@
|
||||
# Determine ipv4 And ipv6 Public IP Addresses
|
||||
|
||||
There are a number of ways to do this. The one that I've settled on is sending a
|
||||
`curl` request to a public URL that was specifically set up to echo back the
|
||||
public IP of the device making the request. There are many such URLs, but the
|
||||
one that I tend to use is `ifconfig.io`.
|
||||
|
||||
When I run this as is, I get something like the following which you may
|
||||
recognize as an _ipv6_ IP address.
|
||||
|
||||
```bash
|
||||
$ curl ifconfig.io
|
||||
2001:db8:3333:4444:5555:6666:7777:8888
|
||||
```
|
||||
|
||||
This is because if ipv6 is available, like it is for me, `curl` is going to
|
||||
prefer that.
|
||||
|
||||
Now, if I'm trying to track down specifically my ipv4 address, I can use the
|
||||
`-4` flag (or `--ipv4`).
|
||||
|
||||
```bash
|
||||
$ curl -4 ifconfig.io
|
||||
73.23.45.157
|
||||
```
|
||||
|
||||
Similarly, I could explicitly specify ipv6 with `-6` or `--ipv6`.
|
||||
|
||||
See `man curl` for more details.
|
||||
51
unix/rename-a-bunch-of-files-by-constructing-mv-commands.md
Normal file
51
unix/rename-a-bunch-of-files-by-constructing-mv-commands.md
Normal file
@@ -0,0 +1,51 @@
|
||||
# Rename A Bunch Of Files By Constructing mv Commands
|
||||
|
||||
I downloaded a bunch of bank statements as PDFs. On the upside they all were
|
||||
consistently named. On the downside they used an unhelpful date format. With a
|
||||
date format that puts year before month before day, the files easily sort
|
||||
alphanumerically. However, these filenames used a date format that put month
|
||||
before day before year.
|
||||
|
||||
Here is a subset of the files
|
||||
|
||||
```bash
|
||||
$ ls *.pdf
|
||||
'012524 Statement.pdf'
|
||||
'012725 Statement.pdf'
|
||||
'022624 Statement.pdf'
|
||||
'022625 Statement.pdf'
|
||||
'032524 Statement.pdf'
|
||||
'032525 Statement.pdf'
|
||||
```
|
||||
|
||||
Notice they are named with `MMDDYY Statement.pdf`. I would instead like for them
|
||||
to be named as `YYYY-MM-DD-statement.pdf`.
|
||||
|
||||
I can generate a series of `mv` statements that then get piped to `sh` which
|
||||
_evaluates_ them. But first, let's do a dry run of a `sed` statement that
|
||||
rearranges the date parts.
|
||||
|
||||
```bash
|
||||
$ ls *.pdf | sed 's/\(..\)\(..\)\(..\) Statement\.pdf/mv "&" "20\3-\1-\2-statement.pdf"/'
|
||||
mv "012524 Statement.pdf" "2024-01-25-statement.pdf"
|
||||
mv "012725 Statement.pdf" "2025-01-27-statement.pdf"
|
||||
mv "022624 Statement.pdf" "2024-02-26-statement.pdf"
|
||||
mv "022625 Statement.pdf" "2025-02-26-statement.pdf"
|
||||
mv "032524 Statement.pdf" "2024-03-25-statement.pdf"
|
||||
mv "032525 Statement.pdf" "2025-03-25-statement.pdf"
|
||||
```
|
||||
|
||||
The way this works is that all the `pdf` files in the current directly get
|
||||
listed out. That gets piped to a `sed` statement that matches on capture groups
|
||||
against the first three pairs of characters (the date parts) in the filenames.
|
||||
It matches on the rest of the filename (` Statement.pdf`). This is then replaced
|
||||
by a `mv `, the full match of the original filename (`&`), and then the new
|
||||
filename made up of the rearranged date parts.
|
||||
|
||||
I can then pipe it to `sh` to run those `mv` commands.
|
||||
|
||||
```bash
|
||||
$ ls *.pdf |
|
||||
sed 's/\(..\)\(..\)\(..\) Statement\.pdf/mv "&" "20\3-\1-\2-statement.pdf"/' |
|
||||
sh
|
||||
```
|
||||
Reference in New Issue
Block a user