203 lines
7.7 KiB
VimL
203 lines
7.7 KiB
VimL
" libdenote plugin for vim
|
|
" This plugin describes basic denote functions. Each one of these functions
|
|
" deals wither with the file-naming scheme (prefixed with scheme_), or with the
|
|
" front matter (prefixed with fm_). All functions are pure, i.e., have no side
|
|
" affects.
|
|
"
|
|
" FILE-NAMING SCHEME {{{1
|
|
"
|
|
" Script-local functions {{{2
|
|
"
|
|
" This function removes illegal characters in parts of the filename.
|
|
function s:santizefnpart(part)
|
|
return a:part->tolower()
|
|
\ ->substitute('[^[:alnum:]]', '-', 'g')
|
|
\ ->substitute('-\+', '-', 'g')
|
|
\ ->substitute('_\+', '_', 'g')
|
|
\ ->substitute('=\+', '=', 'g')
|
|
\ ->substitute('@\+', '@', 'g')
|
|
\ ->trim('-_@=')
|
|
endfunction
|
|
|
|
" API {{{2
|
|
|
|
" Identifier creation
|
|
" This unction generates a fresh denote identifier
|
|
function libdenote#scheme_idgen()
|
|
return exists('*strftime')
|
|
\ ? strftime('%Y%m%dT%H%M%S')
|
|
\ : rand()
|
|
endfunction
|
|
|
|
" Generate file name
|
|
" This function returns the Filename given all components in the file-naming
|
|
" scheme
|
|
"
|
|
" @argument a:ext string: File extension
|
|
" @argument a:id string: Identifier of denote entry
|
|
" @argument a:title string: Title of denote entry
|
|
" @argument a:tags list[string]: List of strings that specify the tags
|
|
" @argument a:sig string: Signature string of denote entry
|
|
function libdenote#scheme_filename(ext, identifier, title='', tags=[], sig='')
|
|
let l:f = s:santizefnpart(a:identifier)
|
|
let l:f ..= len(a:sig) > 0 ? ('==' .. s:santizefnpart(a:sig)) : ''
|
|
let l:f ..= len(a:title) > 0 ? ('--' .. s:santizefnpart(a:title)) : ''
|
|
let l:f ..= len(a:tags) > 0 ? ('__' .. map(copy(a:tags), {_, v -> s:santizefnpart(v)})->join('_')) : ''
|
|
return l:f .. '.' .. a:ext
|
|
endfunction
|
|
|
|
" Retrieve metadata from file name
|
|
" This function returns a dict with the entries 'id', 'title', 'tags', and
|
|
" 'sig.' The value type of 'tags' is a list, the other entries hold strings.
|
|
"
|
|
" @argument a:filename string: Filename or path to file
|
|
function libdenote#scheme_metadata(filename)
|
|
return {
|
|
\ 'id': a:filename->fnamemodify(':t')->matchstr('@@\zs.\{-\}\ze\(==\|--\|__\|\..\)')
|
|
\ ?? a:filename->fnamemodify(':t')->matchstr('^.\{-\}\ze\(==\|--\|__\|\..\)'),
|
|
\ 'title': a:filename->fnamemodify(':t')->matchstr('--\zs.\{-\}\ze\(==\|@@\|__\|\..\)')
|
|
\ ->substitute('-', ' ', 'g'),
|
|
\ 'tags': a:filename->fnamemodify(':t')->matchstr('__\zs.\{-\}\ze\(==\|@@\|--\|\..\)')
|
|
\ ->split('_'),
|
|
\ 'sig': a:filename->fnamemodify(':t')->matchstr('==\zs.\{-\}\ze\(==\|@@\|__\|\..\)')
|
|
\}
|
|
endfunction
|
|
|
|
|
|
" Get path to file from denote identifier
|
|
" This function returns the path to the file the note corresponds to, if it
|
|
" exists, and v:false otherwise.
|
|
"
|
|
" @argument a:dir string: Path to denote directory
|
|
" @argument a:id string: Identifier of denote entry
|
|
function libdenote#scheme_find(dir, id)
|
|
" According to the file-naming scheme, the note id is prefixed with '@@'. If
|
|
" the note id appears at the beginning of the file name, then this prefix is
|
|
" optional. There may exist another field (such as the signature, title, or
|
|
" keywords field) after the note id. These are prefixed with '==', '--', or
|
|
" '__'. If the note id is the last field, then the id is followed by the file
|
|
" extension, e.g., '.md'.
|
|
" (A) First, we get all files that contain the note id as substring.
|
|
" (B) Then we ensure that the note id is followed by another field or by the
|
|
" file extension.
|
|
let l:files = glob(a:dir .. '/*' .. a:id .. '*', 0, v:true)
|
|
\ ->filter('v:val->split("/")[-1] =~ "' .. a:id .. '\\(==\\|--\\|__\\|\\.\\)"')
|
|
\ ->filter('v:val->split("/")[-1] =~ "^' .. a:id .. '\\|@@' .. a:id .. '"')
|
|
return empty(l:files) ? v:false : l:files[0]
|
|
endfunction
|
|
|
|
|
|
" FRONT-MATTER HANDLING {{{1
|
|
"
|
|
" Script-local functions {{{2
|
|
"
|
|
" Put string in double quotes, and remove inside double quotes.
|
|
function s:escapeDQ(s)
|
|
return '"' .. substitute(a:s, '"', '', 'g') .. '"'
|
|
endfunction
|
|
|
|
" Create front matter (yaml)
|
|
function s:md_new_yaml(id, title, tags)
|
|
return [
|
|
\ '---',
|
|
\ 'title: ' .. s:escapeDQ(a:title),
|
|
\ 'date: ' .. strftime('%FT%T%z'),
|
|
\ 'tags: [' .. map(copy(a:tags), {_, t -> s:escapeDQ(t) })->join(', ') .. ']',
|
|
\ 'identifier: ' .. s:escapeDQ(a:id),
|
|
\ '---'
|
|
\ ]
|
|
endfunction
|
|
|
|
" Create front matter (toml)
|
|
function s:md_new_toml(id, title, tags)
|
|
return [
|
|
\ '+++',
|
|
\ 'title ' .. s:escapeDQ(a:title),
|
|
\ 'date ' .. strftime('%FT%T%z'),
|
|
\ 'tags [' .. map(copy(a:tags), {_, t -> s:escapeDQ(t) })->join(', ') .. ']',
|
|
\ 'identifier ' .. s:escapeDQ(a:id),
|
|
\ '+++'
|
|
\ ]
|
|
endfunction
|
|
|
|
" Create front matter (plain)
|
|
function s:plain_new(id, title, tags)
|
|
return [
|
|
\ 'title: ' .. a:title,
|
|
\ 'date: ' .. strftime('%F'),
|
|
\ 'tags: ' .. a:tags->join(' '),
|
|
\ 'identifier: ' .. a:id,
|
|
\ '---------------------------',
|
|
\ ]
|
|
endfunction
|
|
|
|
" Create front matter (org)
|
|
function s:org_new(id, title, tags)
|
|
return [
|
|
\ '#+title: ' .. a:title,
|
|
\ '#+date: [' .. strftime('%F %a %R') .. ']',
|
|
\ '#+filetags: :' .. map(copy(a:tags), {_, t -> substitute(t, ':', '', 'g') })->join(':') .. ':',
|
|
\ '#+identifier: ' .. a:id,
|
|
\ ]
|
|
endfunction
|
|
|
|
" API {{{2
|
|
|
|
" Create front matter
|
|
" This function generates a front matter. It may use the global variable
|
|
" g:denote_fm_md_type.
|
|
"
|
|
" @argument a:ext string: Generate front matter for a file of this
|
|
" extension
|
|
" @argument a:id string: Identifier of the denote note
|
|
" @argument a:title string: Title of the denote note
|
|
" @argument a:tags list[string]: List of strings that specify the tags
|
|
" @argument a:md_type string: Any of 'yaml' (default) or 'toml', for
|
|
" markdown front matter
|
|
" @return: List of strings with a line-per item that describes the front matter
|
|
function libdenote#fm_gen(ext, id, title, tags, md_type='yaml')
|
|
return a:ext == 'org' ? s:org_new(a:id, a:title, a:tags)
|
|
\ : a:ext == 'txt' ? s:plain_new(a:id, a:title, a:tags)
|
|
\ : a:md_type == 'toml' ? s:md_new_toml(a:id, a:title, a:tags)
|
|
\ : s:md_new_yaml(a:id, a:title, a:tags)
|
|
endfunction
|
|
|
|
" Alter front matter
|
|
" This function returns the (modified) a:fm front matter. The argument a:mod
|
|
" of type dict specifies the fields to be updated. If a:mod contains the key
|
|
" 'date', then the date field will be updated. If it contains the key 'id',
|
|
" then the identifier will be updated with the value a:mod.id. Similar for the
|
|
" title (key 'title'), and the tags (key 'tags'). For the tags, the value type
|
|
" is a list of strings.
|
|
"
|
|
" @argument a:fm list[string] List of strings describes a frontmatter
|
|
" @argument a:mod dict Dictionary to control updates
|
|
" @return: List of strings with a line-per item that describes the front matter
|
|
function libdenote#fm_alter(fm, mod)
|
|
let l:md_type = a:fm[0] == '+++' ? 'toml' : 'yaml'
|
|
let l:ext = { n -> n == 4 ? 'org' : n == 5 ? 'txt' : 'md'}(len(a:fm))
|
|
let l:new = copy(a:fm)
|
|
let l:repl = libdenote#fm_gen(l:ext,
|
|
\ has_key(a:mod, 'id') ? a:mod.id : '',
|
|
\ has_key(a:mod, 'title') ? a:mod.title : '',
|
|
\ has_key(a:mod, 'tags') ? a:mod.tags : [],
|
|
\ g:denote_fm_md_type)
|
|
if has_key(a:mod, 'date')
|
|
let l:ididx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?date"')
|
|
call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?date" ? l:repl[l:ididx] : v})
|
|
endif
|
|
if has_key(a:mod, 'id')
|
|
let l:ididx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?identifier"')
|
|
call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?identifier" ? l:repl[l:ididx] : v})
|
|
endif
|
|
if has_key(a:mod, 'title')
|
|
let l:titleidx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?title"')
|
|
call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?title" ? l:repl[l:titleidx] : v})
|
|
endif
|
|
if has_key(a:mod, 'tags')
|
|
let l:tagsidx = indexof(l:repl, 'v:val =~ "^\\(#+file\\)\\?tags"')
|
|
call map(l:new, {_, v -> v =~ "^\\(#+file\\)\\?tags" ? l:repl[l:tagsidx] : v})
|
|
endif
|
|
return l:new
|
|
endfunction
|