First Post ™

4 June 2020

First Post ™

Test post don't upvote.

Vim bros

One thing I need to do for every post is put a title and a date on it in the frontmatter.

That's annoying and easy to forget. Also if I update the post I need to change these too.

Right now it has to look like this:

+++
title = "First Post ™"
date = 2020-06-03
[extra]
background = "linux.png"
+++

The background image has to be put there by hand since I have no way to deduce the image's name, if there is one from the post.

So I went searching and found this, under "Automatically update timestamps".

" If buffer modified, update any 'Last modified: ' in the first 20 lines.
" 'Last modified: ' can have up to 10 characters before (they are retained).
" Restores cursor and window position using save_cursor variable.
function! LastModified()
  if &modified
    let save_cursor = getpos(".")
    let n = min([20, line("$")])
    keepjumps exe '1,' . n . 's#^\(.\{,10}Last modified: \).*#\1' .
          \ strftime('%a %b %d, %Y  %I:%M%p') . '#e'
    call histdel('search', -1)
    call setpos('.', save_cursor)
  endif
endfun
autocmd BufWritePre * call LastModified()

Which is fine and all but it's not good enough just yet, first it makes a guess the 20 is the number of lines that matter, in my case there is a better alternative, because the zone where this matters in inside +++ I can just fetch these.

call cursor(1, 1) " Put the cursor at the top
let l:st = search('+++', 'c') " Find the line where the first +++ appears
let l:end = search('+++') " Find the second +++

Then I just needed to get the current title and current date.

let l:title_line = search('^#[^#]') " Get the title line
let l:title = getline(l:title_line) " Get the text at that line
let l:title = substitute(l:title, "^#[ ]*", "", "") " Remove the # at the start
let l:now = strftime('%F') " Get the current date

With every thing ready it was just a matter of adapting the original find and replace, to use the correct start and end lines, l:st and l:end, as well as the correct pattern.

keepjumps exe l:st . ',' . l:end . 's/^title =.*/title = "' . l:title . '"/'
keepjumps exe l:st . ',' . l:end . 's/^date =.*/date = ' . l:now . '/'

The only other thing left to do was handle the obvious error. There might not be any +++ yet when I save, if I forget to add them (which I will) and for that I just had to insert a check and create the boiler plate. This also gets around the problem of there being random +++ sequences in the article itself, like this one does.

if l:st != 1 " front matter must be at line 1
    call append(0, ['+++',
                \ 'title =',
                \ 'date = ',
                \ '#[extra]',
                \ '#background = ""',
                \ '+++'])
    let l:st = 1  " I hard code these so that the find and replace
    let l:end = 6 " can find this zone.
endif

The final code looks like this:

function! BlogPostModified()
    if &modified
        let l:save_cursor = getpos(".")
        call cursor(1, 1)
        let l:st = search('+++', 'c')
        let l:end = search('+++')
        let l:title_line = search('^#[^#]')
        let l:title = getline(l:title_line)
        let l:title = substitute(l:title, "^#[ ]*", "", "")
        let l:now = strftime('%F')
        if l:st != 1
            call append(0, ['+++',
                        \ 'title =',
                        \ 'date = ',
                        \ '#[extra]',
                        \ '#background = ""',
                        \ '+++'])
            let l:st = 1
            let l:end = 6
        endif
        keepjumps exe l:st . ',' . l:end . 's/^title =.*/title = "' . l:title . '"/'
        keepjumps exe l:st . ',' . l:end . 's/^date =.*/date = ' . l:now . '/'
        call histdel('search', -1)
        call setpos('.', save_cursor)
    endif
endfun
autocmd BufWritePre content/*.md call BlogPostModified()

Done and I never have to worry about that in my life. Until the script fails...

Result

first_post_gif