" 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