-
Correcting Date Tags using sed in an Entire Obsidian Vault
I’ve been using Obsidian for about 8 months. It wasn’t until today that I realized I’ve been using hierarchical tags incorrectly since day one!
The Problem
In my templater templates I was using the following:
1#<% tp.date.now("YYYY-MM-DD") %>/<% tp.date.now("YYYY-MM") %>/<% tp.date.now("YYYY") %> #<% tp.date.now("YYYY-MM-DD") %>The result of this is:
1#2022-03-17/2022-03/2022 #2022-03-17Unfortunately there are two problems with this. First, there is no need for that extra tag at the end; it’s already covered by the first combo tag.
Second, the hierarchy is backwards! This leads to:
123456├── 2022-03-16│ └── 2022-03│ └── 2022└── 2022-03-17└── 2022-03└── 2022Houston, we have a problem! What I really was after was a tag structure like this:
1234└── 2022└── 2022-03├── 2022-03-16└── 2022-03-17The Fix
Thankfully, Obsidian stores all of the files on the filesystem natively, which means you can manipulate them using whatever tools you’d like.
Enter sed, a stream editor. Using sed and find, I was able to quickly resolve both of the issues above.
If I’d taken a little more time I would have handled both problems in one shot, but I did not. I fixed problem 2, then problem 1.
First, I cd’d into the root directory of my vault. Then…
1234567891011# Find markdown files, feed them to sed,# edit the file "in place" (modify them directly)# We use capture groups (the parentheses) to# essentially reverse the order of the tagsfind . -type f -name '*.md' -print0 | xargs -0 sed -E -i "" "s/#([0-9]{4}-[0-9]{2}-[0-9]{2})\/([0-9]{4}-[0-9]{2})\/([0-9]{4})/#\3\/\2\/\1/g"# Find #YYYY/YYYY-MM/YYYY-MM-DD #YYYY-MM-DD,# capturing the first tag to "\1"# Replace with the capture group (thereby dropping the second tag)find . -type f -name '*.md' -print0 | xargs -0 sed -E -i "" "s/(#[0-9]{4}\/[0-9]{4}-[0-9]{2}\/[0-9]{4}-[0-9]{2}-[0-9]{2}) #[0-9]{4}-[0-9]{2}-[0-9]{2}/\1/g"Finally, I updated all of my templater templates to drop the second tag and fix the first to be in the right order.
Obsidian picked up all of the changes immediately, which were reflected in the tag pane. Much cleaner!
-
Editing a DEVONThink Markdown File in VS Code
This is a sister post to Editing Web Content in VS Code.
This simple macro opens the current file (if Markdown) in VS Code, with the Markdown Preview Enhanced pane open on the right automatically.
-
Editing Web Content in VS Code
Okay, so it’s not quite as amazing as the title suggests, but this solution is worth sharing.
I work a lot in Bookstack. It’s a fantastic documentation system built with Laravel. I edit all pages in markdown. There is a markdown pane on the left and a preview on the right. I love the UI (shown below) but unfortunately I cannot move around in the editor like I can in PHPStorm or VS Code, where I have Vim support.
I have a Chrome extension called Wasavi, which is incredible for editing text areas in an inline virtual Vim window. Unfortunately, though, it doesn’t show realtime feedback in the preview pane until I save and close out of the virtual editor.
It occurred to me it’d be useful to just pop into VS Code quickly to edit the doc, then bring those edits back into the Bookstack editor in Chrome. At first I dismissed the idea as being “too much work.” After thinking about it for a moment, though, I realized I could MacGyver this relatively quickly with my favorite Mac application, Keyboard Maestro.
Here’s what I came up with. As with most things, I am sure I’ll tweak this over time to make it better, faster, less “error”-prone, etc. I have added a README comment and renamed most actions so it’s (hopefully) easy to understand the approach.
-
One-handed Gmail Inbox Culling with Keyboard Maestro
Keyboard Maestro proves, once again, why it’s at the top spot on my most recommended Mac applications list.
My normal procedure for culling (deleting unwanted) emails in Gmail is (with list on left and email preview on right):
- Open the first email (opens in right pane)
- If deleting, press “#” (⇧3)
- Else, if skipping, press “j” to move down to the next email (standard Gmail behavior, I think; and it’s intuitive if you’re used to Vim)
- Repeat steps 2 and 3.
I realized today how much I dislike hitting “#” with my left hand while browsing with my right.
-
Testing Cookie Modification in Laravel 8
If there’s a better way to pass a cookie from one request to another, in a phpunit feature test in Laravel, please let me know! Here’s one way to handle it:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546<?phpnamespace Tests\Feature;use Illuminate\Cookie\CookieValuePrefix;use Tests\TestCase;class DiveDeeperTest extends TestCase{/*** Tests if the diveDeeper route appends the passed value to the cookie arr.* @return void*/public function test_it_updates_path_cookie(){// Post a deeperPath to the diveDeeper route (with a starter cookie).// This should append the "deeperPath" value to the "path" cookie array.$resp = $this->withCookie('path', serialize(['/']))->post(route('diveDeeper'), ['deeperPath' => 'Users']);// Assert that the cookie has been modified.$resp->assertCookie('path', serialize(['/', 'Users']));// If we make another post request here, it won't include our// modified "path" cookie. We have to retrieve it, then pass it// through to the next request.// Decrypt the cookie (now modified by the diveDeeper route).$cookieValue = CookieValuePrefix::remove(app('encrypter')->decrypt($resp->getCookie('path')->getValue(), false));// Hit the diveDeeper route again using the modified cookie value.$resp = $this->withCookie('path', $cookieValue)->post(route('diveDeeper'), ['deeperPath' => 'adam']);// Assert that the cookie has been modified again.$resp->assertCookie('path', serialize(['/', 'Users', 'adam']));// Etc...}} -
Any Random Saturday Using Faker
Here’s a quick one, folks. I’m using Faker in Laravel factories to generate realistic data. I have a “week end date” field in one of my models that must always be a Saturday. Here’s how I generate a unique random Saturday:
1date('Y-m-d', strtotime('next Saturday ' . $this->faker->unique->date())) -
Using “php artisan serve” with xdebug and PHPStorm
This is more of a person note for myself. This posts assumes some knowledge of php, Laravel, artisan, Homebrew, and xdebug.
I’ve been using php artisan serve to serve Laravel applications locally. It’s quick and it works well.
It’s probably not a great solution if you’re working with other people on a project, or if you want to implement a good CD/CI workflow. Having said that, it’s what I’m using today, so I figured I’d document how I got xdebug working with it (on my Mac).
-
Using Carbon to Generate Random Dates and Times
There are a number of ways to generate random dates/times in PHP. Here are some examples using the Carbon library:
1234567891011// Between now and 180 days agoCarbon::today()->subDays(rand(0, 180))> 2021-09-02 00:00:00> 2021-06-23 00:00:00> 2021-05-23 00:00:00// Between now and 180 days ago with random timeCarbon::today()->subDays(rand(0, 179))->addSeconds(rand(0, 86400));> 2021-06-11 22:35:03> 2021-10-24 13:19:53> 2021-06-09 20:47:44 -
Adding a Global Function to Cypress for a Date {num} Months Ago
I’m working on a COVID-19 booster shot registration form. On this form I ask the user when they received their last COVID-19 vaccine shot. This is three simple fields (the month values are 01, 02, 03, etc.):
Each vaccine brand (Pfizer, Moderna, J&J) has different eligibility criteria as it relates to the time since the individual received their original vaccine series. Pfizer, for example, is 6 months. My form needs to reject a user who says they received Pfizer less than 6 months ago.
Using Cypress I wanted some test coverage of this functionality. Original I had hard-coded a date value of 5 months ago:
123cy.get('select[name="datelastvax_month"]').select('08');cy.get('select[name="datelastvax_day"]').select('01');cy.get('select[name="datelastvax_year"]').select('2021');As you can imagine this would work well for a while. Fast-forward a few months from now… the time ago will eventually be more than 6 months ago. I don’t want to have to keep updating this code with a recent date. The obvious solution is to dynamically choose the dates based on the current date. A global helper function would be ideal as I need to use this in many tests. Here’s a simple solution:
Step 1: Define the Function in index.js
The support/index.js file is loaded before each Cypress run. Creating a global function here is a quick way to introduce a reusable piece of code.
12345678910111213141516171819202122/*** Returns a simple object for {num} months ago, containing YYYY, MM, DD.** @param {number} num* Number of months to subtract from current date.** @return object* Object containing three properties (year:YYYY, month:MM, day:DD)*/cy.getDateMonthsAgo = (num) => {const dateObj = new Date(new Date().getFullYear(),new Date().getMonth() - num,new Date().getDate());return {year: dateObj.getFullYear().toString(),month: ('0' + (dateObj.getMonth() + 1)).slice(-2),day: ('0' + dateObj.getDate()).slice(-2),}}There are many solutions to handle getting a date from {num} months ago. If you start looking around for “What is a month, really?” you will find many different opinions. The code I’m using above responds like most humans do: give the same day of the month from {num} months ago. With a {num} of 5 (five months ago), on October 25, 2021 the result would be May 25, 2021.
WARNING: Please note the following:
12345new Date(2021, 1, 28) ==> February 28, 2021new Date(2021, 1, 29) ==> March 1, 2021new Date(2021, 1, 30) ==> March 2, 2021new Date(2021, 1, 31) ==> March 3, 2021new Date(2021, 1, 32) ==> March 4, 2021Step 2: Use the Function
1234const fiveMonthsAgo = cy.getDateMonthsAgo(5);cy.get('select[name="datelastvax_month"]').select(fiveMonthsAgo.month);cy.get('select[name="datelastvax_day"]').select(fiveMonthsAgo.day);cy.get('select[name="datelastvax_year"]').select(fiveMonthsAgo.year); -