diff --git a/autoload/libdenote.vim b/autoload/libdenote.vim new file mode 100644 index 0000000..f91ec28 --- /dev/null +++ b/autoload/libdenote.vim @@ -0,0 +1,195 @@ +" 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. For any of the +" arguments a:id, a:title, and a:tags, if the argument is specified (for the +" tag, it may also be an empty list), then the value of the argument is used as +" a replacement in the input front matter a:fm. +" +" @argument a:fm list[string] List of strings describes a frontmatter +" @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] or v:false: +" List of strings that specify the tags +" @return: List of strings with a line-per item that describes the front matter +function libdenote#fm_alter(fm, ext, id='', title='', tags=v:false) + let l:new = copy(a:fm) + let l:repl = libdenote#fm(a:ext, a:id ?? '', a:title ?? '', a:tags ?? []) + if a:id + let l:ididx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?identifier"') + call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?identifier" ? l:repl[l:ididx] : v}) + endif + if a:title + let l:titleidx = indexof(l:repl, 'v:val =~ "^\\(#+\\)\\?title"') + call map(l:new, {_, v -> v =~ "^\\(#+\\)\\?title" ? l:repl[l:titleidx] : v}) + endif + if type(a:tags) == v:t_list + 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 diff --git a/doc/denote.txt b/doc/denote.txt index 99d0207..a9ea605 100644 --- a/doc/denote.txt +++ b/doc/denote.txt @@ -7,13 +7,19 @@ This is the documentation for the denote plugin. ============================================================================== CONTENTS *vim-denote* *denote* -1. Introduction |denote-intro| -2. Commands |denote-commands| - 2.1 Universal commands |denote-universal-commands| - 2.2 Note command |denote-note-command| - 2.3 Denote-list commands |denote-list-commands| -3. Settings |denote-settings| -4. Keys |denote-keys| + 1. Introduction |denote-intro| + 2. Commands |denote-commands| + 2.1 Universal commands |denote-universal-commands| + 2.2 Note command |denote-note-command| + 2.3 Denote-list commands |denote-list-commands| + 3. Settings |denote-settings| + 4. Keys |denote-keys| + + Appendices + A. The libdenote plugin |libdenote| + A.1 File-naming functions |libdentoe-filenaming| + A.2 Front-matter functions |libdenote-frontmatter| + B. Package API |denote-api| ============================================================================== INTRODUCTION *denote-intro* @@ -245,4 +251,64 @@ dd Delete the selected entry. {Visual}d Delete the visually selected entries. +============================================================================== +APPENDIX A - THE LIBDENOTE PLUGIN *libdenote* + +The present package has as constituent the libdenote plugin for basic +denote-centered operations. That plugin is given by the file +autoload/libdenote.vim. Each function in this plugin is pure, i.e., produces +no side effects. There are two classes of functions: functions concerning the +file-naming scheme (prefixed with scheme_, see |libdenote-filenaming|) and +functions concerning the front matter (prefixed with fmt_, see +|libdenote-frontmatter|). + + *libdenote-filenaming* +File-naming functions~ + *libdenote#scheme_idgen()* +libdenote#scheme_idgen() + This function generates a new identifier. If the function |strftime()| + is available, than strftime(%Y%m%dT%H%M%S) is used, otherwise a random + identifier is generated using |rand()|. + + *libdenote#scheme_filename()* +libdenote#scheme_filename({ext}, {id}, {title}, {tags}, {sig}) + With this, the file name of the denote entry with the given metadata + is returned. The parameter {ext} describes the extension of the file + is is one of 'md', 'txt', or 'org'. The parameter {id} is the denote + identifier, possibly generated via |libdenote#scheme_idgen()|. The + {title} parameter is optional and describes the title of the note. By + default, it is set to the empty string ''. The {tags} parameter is + optional as well, is a list of tags associated to the note. The + default value is the empty list []. Also, the {sig} parameter is + optional, and used to describe the signature of the entry. + + *libdenote#scheme_metadata()* +libdenote#scheme_metadata({filename}) + This function returns the metadata of the file given by {filename}. + This parameter may describe the path to the file, or the tail only. + The returned |dict| has the keys 'id' for the identifier, 'title' for + the title, 'tags' for the list of tags, and 'sig' for the signature. + + *libdenote-frontmatter* +Front-matter functions~ + *libdenote#fm_gen()* +libdenote#fm_gen({ext}, {id}, {title}, {tags}, {md_type}) + This function returns the list of lines for the front matter that + stores the given metadata. The paramter {ext} is the extension of the + file, the parameter {id} the identifier, the parameter {title}, the + title of the note, and {tags} the optional parameter as a list of tags + associated to the note. Per default {tags} is set to the empty list + []. Finally, {md_type} descries the format to be used in markdown. For + markdown files (extension 'md'), two formats are possible: 'yaml' and + 'toml'. The former format is used per default. + + *libdenote#fm_alter()* +libdenote#fm_alter({fm}, {ext}, {id}, {title}, {tags}, {md_type}) + Similar to |libdenote#fm_gen()|, this function returns a list + containing the lines of a front matter. In contrast to the above + function, this functions takes a front matter {fm} as base (again, + given as list of lines), and alters the title or tags associated with + the note. The arguments {title} and {tags} are optional. When set, + then {fm} is returned with the corresponding replaced lines. + vim:tw=78:sw=4:ts=8:noet:ft=help:norl: