diff --git a/autoload/wiki/buffer.vim b/autoload/wiki/buffer.vim index 23ea7539..9817ad55 100644 --- a/autoload/wiki/buffer.vim +++ b/autoload/wiki/buffer.vim @@ -76,9 +76,9 @@ function! s:init_buffer_commands() abort " {{{1 if b:wiki.in_journal command! -buffer -count=1 WikiJournalPrev call wiki#journal#go(-) command! -buffer -count=1 WikiJournalNext call wiki#journal#go() - command! -buffer WikiJournalCopyToNext call wiki#journal#copy_note() - command! -buffer WikiJournalToWeek call wiki#journal#freq('weekly') - command! -buffer WikiJournalToMonth call wiki#journal#freq('monthly') + command! -buffer WikiJournalCopyToNext call wiki#journal#copy_to_next() + command! -buffer WikiJournalToWeek call wiki#journal#go_to_frq('weekly') + command! -buffer WikiJournalToMonth call wiki#journal#go_to_frq('monthly') endif endfunction diff --git a/autoload/wiki/cache.vim b/autoload/wiki/cache.vim index ea61bdf9..62ad7a9f 100644 --- a/autoload/wiki/cache.vim +++ b/autoload/wiki/cache.vim @@ -147,11 +147,11 @@ function! s:cache.init(path, opts) dict abort " {{{1 let new.path = a:path let new.ftime = -1 let new.default = a:opts.default + let new.__validated = 0 + let new.__validation_value = deepcopy(a:opts.validate) if a:opts.persistent - call extend(new, s:cache_persistent) - call new.validate(a:opts.validate) - return new + return extend(new, s:cache_persistent) endif return extend(new, s:cache_volatile) @@ -163,14 +163,13 @@ let s:cache_persistent = { \ 'type': 'persistent', \ 'modified': 0, \} -function! s:cache_persistent.validate(value) dict abort " {{{1 - let self.__validation_value = deepcopy(a:value) +function! s:cache_persistent.validate() dict abort " {{{1 + let self.__validated = 1 + if type(self.__validation_value) == v:t_dict let self.__validation_value._version = s:_version endif - call self.read() - if empty(self.data) let self.data.__validate = deepcopy(self.__validation_value) return @@ -245,6 +244,10 @@ function! s:cache_persistent.read() dict abort " {{{1 endif call extend(self.data, l:data, 'keep') + + if !self.__validated + call self.validate() + endif endfunction " }}}1 diff --git a/autoload/wiki/date.vim b/autoload/wiki/date.vim index 2ad42f1e..6db6fd70 100644 --- a/autoload/wiki/date.vim +++ b/autoload/wiki/date.vim @@ -4,23 +4,13 @@ " Email: karl.yngve@gmail.com " -function! wiki#date#format(date, format) abort " {{{1 - return s:date(a:date, a:format) -endfunction - -" }}}1 -function! wiki#date#offset(date, offset) abort " {{{1 - return s:date_offset(a:date, a:offset) -endfunction - -" }}}1 function! wiki#date#get_day_of_week(date) abort " {{{1 - return s:date(a:date, '%u') + return strftime('%u', strptime('%F', a:date)) endfunction " }}}1 function! wiki#date#get_week(date) abort " {{{1 - return s:date(a:date, '%V') + return strftime('%V', strptime('%F', a:date)) endfunction " }}}1 @@ -33,26 +23,30 @@ function! wiki#date#get_week_dates(...) abort " {{{1 let l:dow = wiki#date#get_day_of_week(l:date) let l:range = range(1-l:dow, 7-l:dow) elseif a:0 == 2 - let l:week = a:1 - let l:year = a:2 - let l:date = l:year . '-01-01' - - let l:dow = wiki#date#get_day_of_week(l:date) - let l:first_week = wiki#date#get_week(l:date) - if l:first_week > 1 - let l:first_week = 0 - endif - - let l:ndays = 7*(l:week - l:first_week) - (l:dow - 1) - let l:range = range(l:ndays, l:ndays+6) + let l:date = wiki#date#get_week_first_date(a:1, a:2) + let l:range = range(0, 6) else return [] endif - return map(l:range, 's:date_offset(l:date, v:val . '' days'')') + return map(l:range, { _, x -> s:date_offset(l:date, x) }) endfunction " }}}1 +function! wiki#date#get_week_first_date(week, year) abort " {{{1 + let l:date_first = a:year . '-01-01' + let l:dow = wiki#date#get_day_of_week(l:date_first) + let l:first_week = wiki#date#get_week(l:date_first) + if l:first_week > 1 + let l:first_week = 0 + endif + + let l:offset_days = 7*(a:week - l:first_week) - l:dow + 1 + return s:date_offset(l:date_first, l:offset_days) +endfunction + +" }}}1 + function! wiki#date#get_month_name(month) abort " {{{1 return get(g:wiki_month_names, a:month-1) endfunction @@ -70,8 +64,10 @@ endfunction " }}}1 function! wiki#date#get_month_days(month, year) abort " {{{1 - return map(range(1, wiki#date#get_month_size(a:month, a:year)), - \ 'printf(''%4d-%02d-%02d'', a:year, a:month, v:val)') + return map( + \ range(1, wiki#date#get_month_size(a:month, a:year)), + \ { _, x -> printf('%4d-%02d-%02d', a:year, a:month,x) } + \) endfunction " }}}1 @@ -88,13 +84,16 @@ function! wiki#date#get_month_decomposed(month, year) abort " {{{1 let l:days_pre = l:first_monday > 1 ? l:days[:l:first_monday-2] : [] let l:days_post = l:remaining_days > 0 ? l:days[-l:remaining_days:] : [] - let l:weeks = map(range(l:number_of_weeks), - \ 'printf(''%4d_w%02d'', a:year, l:first_week + v:val)') + let l:weeks = map( + \ range(l:number_of_weeks), + \ { _, x -> printf('%4d-w%02d', a:year, l:first_week + x) } + \) return l:days_pre + l:weeks + l:days_post endfunction " }}}1 + function! wiki#date#format_to_regex(format) abort " {{{1 let l:regex = substitute(a:format, '%[ymdVU]', '\\d\\d', 'g') return substitute(l:regex, '%Y', '\\d\\d\\d\\d', '') @@ -107,16 +106,12 @@ function! wiki#date#parse_format(date, format) abort " {{{1 \ 'Y' : ['year', 4], \ 'm' : ['month', 2], \ 'd' : ['day', 2], - \ 'V' : ['week', 2], + \ 'V' : ['week_iso', 2], \ 'U' : ['week', 2], \} let l:rx = '%[' . join(keys(l:keys), '') . ']' - let l:result = { - \ 'year': '1970', - \ 'month': '01', - \ 'day': '01', - \} + let l:result = {} let l:date = copy(a:date) let l:format = copy(a:format) while v:true @@ -133,47 +128,75 @@ function! wiki#date#parse_format(date, format) abort " {{{1 let l:result.year = '20' . l:result.year endif - if has_key(l:result, 'week') - let l:tmp_date = printf('%s-01-10', l:result.year) - let l:dow = wiki#date#get_day_of_week(l:tmp_date) - let l:week = wiki#date#get_week(l:tmp_date) - let l:offset = 7*(l:result.week - l:week) - l:dow + 1 - let l:date = s:date_offset(l:tmp_date, l:offset . ' days') - let l:result.month = l:date[5:7] - let l:result.day = l:date[8:] - endif - return l:result endfunction " }}}1 -" -" Utility functions for running GNU date or similar shell commands -" -function! s:date(date, format) abort " {{{1 - if s:gnu_date - return wiki#jobs#capture(printf(s:cmd_date - \ . ' +"%s" -d "%s"', a:format, a:date))[0] - else - return wiki#jobs#capture(printf(s:cmd_date - \ . ' -j -f "%s" "%s" +"%s"', '%Y-%m-%d', a:date, a:format))[0] +function! wiki#date#strptime(format, timestring) abort " {{{1 + let l:dd = wiki#date#parse_format(a:timestring, a:format) + + if !has_key(l:dd, 'year') | return 0 | endif + + if has_key(l:dd, 'week_iso') + return s:strptime_weekly(l:dd.year, l:dd.week_iso) endif + + if !has_key(l:dd, 'month') | return 0 | endif + + return strptime('%F', + \ printf('%s-%s-%s', l:dd.year, l:dd.month, get(l:dd, 'day', 1))) endfunction " }}}1 -function! s:date_offset(date, offset) abort " {{{1 - if s:gnu_date - return wiki#jobs#capture(printf(s:cmd_date - \ . ' +%%F -d "%s +%s"', a:date, a:offset))[0] - else - throw 'Not implemented' + +function! s:strptime_weekly(year, week_target) abort " {{{1 + " There's no easy way to get timestamp from the weekly format, but it is easy + " to format a date into a weekly format. So we can get a valid timestamp by + " inverting the problem. + + let l:start = strptime('%F', a:year . '-01-01') + let l:end = strptime('%F', a:year . '-12-31') + + let l:week_start = strftime('%V', l:start) + if l:week_start > 1 + let l:week_start = 0 + endif + let l:week_end = strftime('%V', l:end) + if l:week_end <= 1 + let l:week_end += 52 + endif + if empty(a:week_target) + \ || a:week_target < l:week_start + \ || a:week_target > l:week_end + return 0 endif + + let l:delta = (l:end - l:start)/53 + let l:timestamp = l:start + a:week_target*l:delta + let l:iters = 0 + while l:iters < 10 + let l:iters += 1 + let l:week_current = strftime('%V', l:timestamp) + if l:week_current == a:week_target + return l:timestamp + elseif l:week_current < a:week_target + let l:timestamp += l:delta + else + let l:timestamp -= l:delta + endif + endwhile + + return 0 endfunction " }}}1 +function! s:date_offset(date, offset_days) abort " {{{1 + if a:offset_days == 0 | return a:date | endif -let s:cmd_date = get(g:, 'wiki_date_exe', 'date') -let s:gnu_date = match(wiki#jobs#cached(s:cmd_date . ' --version'), 'GNU') >= 0 + let l:timestamp = strptime('%F', a:date) + let l:timestamp += 86400*a:offset_days + return strftime('%F', l:timestamp) +endfunction -" vim: fdm=marker sw=2 +" }}}1 diff --git a/autoload/wiki/journal.vim b/autoload/wiki/journal.vim index ef405248..74e42b64 100644 --- a/autoload/wiki/journal.vim +++ b/autoload/wiki/journal.vim @@ -4,77 +4,92 @@ " Email: karl.yngve@gmail.com " -function! wiki#journal#make_note(...) abort " {{{1 - let l:date = (a:0 > 0 ? a:1 - \ : strftime(g:wiki_journal.date_format[g:wiki_journal.frequency])) +function! wiki#journal#open(...) abort " {{{1 + " Open a journal entry. + " + " Takes one optional argument: + " date_string: A date string formatted according to the frequency format + " rules. + " + " With no arguments: Go to the current journal entry. This is usually the + " current date, but the g:wiki_journal.frequency setting has an effect here. + + let l:date = a:0 > 0 + \ ? a:1 + \ : strftime(s:date_format[g:wiki_journal.frequency]) call wiki#url#parse('journal:' . l:date).follow() endfunction " }}}1 -function! wiki#journal#copy_note() abort " {{{1 - let l:next = s:get_next_entry() - - let l:next_entry = wiki#paths#s(printf('%s/%s.%s', - \ b:wiki.root_journal, l:next, b:wiki.extension)) - if !filereadable(l:next_entry) - execute 'write' l:next_entry - endif +function! wiki#journal#go(step) abort " {{{1 + let l:node = wiki#journal#get_current_node() + if empty(l:node) | return | endif - call wiki#url#parse('journal:' . l:next).follow() -endfunction + let l:frq = wiki#journal#get_node_frq(l:node) + let l:nodes = wiki#journal#get_all_nodes(l:frq, [l:node]) -" }}}1 -function! wiki#journal#go(step) abort " {{{1 - let l:links = s:get_links() - let l:index = index(l:links, expand('%:t:r')) + let l:index = index(l:nodes, l:node) let l:target = l:index + a:step - - if l:target >= len(l:links) || l:target < 0 + if l:target >= len(l:nodes) || l:target < 0 return endif + let l:target_node = l:nodes[l:target] + let l:target_date = wiki#journal#node_to_date(l:target_node)[0] - call wiki#url#parse('journal:' . l:links[l:target]).follow() + call wiki#journal#open(l:target_date) endfunction " }}}1 -function! wiki#journal#freq(frq) abort " {{{1 - if a:frq ==# 'daily' - return - endif - if a:frq ==# 'weekly' && g:wiki_journal.frequency !=# 'daily' +function! wiki#journal#go_to_frq(frq) abort " {{{1 + let [l:timestamp, l:frq] = wiki#journal#node_to_timestamp() + if l:frq ==# a:frq | return | endif + if l:timestamp == 0 + call wiki#log#warn( + \ 'Current file is not a valid journal node!', + \ expand('%:p')) return endif - if a:frq ==# 'monthly' && g:wiki_journal.frequency ==# 'monthly' + + if (a:frq ==# 'daily' && g:wiki_journal.frequency !=# 'daily') + \ || (a:frq ==# 'weekly' && g:wiki_journal.frequency ==# 'monthly') return endif - let l:filedate = expand('%:t:r') - let l:fmt = g:wiki_journal.date_format.daily - let l:rx = wiki#date#format_to_regex(l:fmt) - let l:date = l:filedate =~# l:rx ? l:filedate : strftime(l:fmt) + call wiki#journal#open(strftime(s:date_format[a:frq], l:timestamp)) +endfunction - call wiki#url#parse('journal:' - \ . wiki#date#format(l:date, g:wiki_journal.date_format[a:frq])).follow() +" }}}1 +function! wiki#journal#copy_to_next() abort " {{{1 + let [l:date, l:frq] = wiki#journal#node_to_date() + if empty(l:date) | return | endif + + " Copy current note to next date - only if it does not exist + let l:next_date = wiki#journal#get_next_date(l:date, l:frq) + let l:node = wiki#journal#date_to_node(l:next_date)[0] + let l:path = s:node_to_path(l:node) + if !filereadable(l:path) + execute 'write' l:path + endif + + call wiki#journal#open(l:next_date) endfunction " }}}1 function! wiki#journal#make_index() " {{{1 - let l:fmt = g:wiki_journal.date_format[g:wiki_journal.frequency] - let l:rx = wiki#date#format_to_regex(l:fmt) - let l:entries = s:get_links_generic(l:rx, l:fmt) - - let l:sorted_entries = {} - for entry in entries - let date = wiki#date#parse_format(entry, g:wiki_journal.date_format.daily) - if has_key(sorted_entries, date.year) - let year_dict = sorted_entries[date.year] - if has_key(year_dict, date.month) - call add(year_dict[date.month], entry) + let l:nodes = wiki#journal#get_all_nodes(g:wiki_journal.frequency) + + let l:grouped_nodes = {} + for l:node in l:nodes + let l:date = wiki#date#parse_format(l:node, g:wiki_journal.date_format.daily) + if has_key(grouped_nodes, l:date.year) + let year_dict = grouped_nodes[l:date.year] + if has_key(year_dict, l:date.month) + call add(year_dict[l:date.month], l:node) else - let year_dict[date.month] = [entry] + let year_dict[l:date.month] = [l:node] endif else - let sorted_entries[date.year] = {date.month:[entry]} + let grouped_nodes[l:date.year] = {l:date.month:[l:node]} endif endfor @@ -83,22 +98,22 @@ function! wiki#journal#make_index() " {{{1 \ ? 'journal:' \ : '/' . g:wiki_journal.name . '/' - for year in sort(keys(sorted_entries)) - let l:month_dict = sorted_entries[year] - put ='# ' . year + for l:year in sort(keys(l:grouped_nodes)) + let l:month_dict = l:grouped_nodes[l:year] + put ='# ' . l:year put ='' - for month in sort(keys(month_dict)) - let entries = month_dict[month] - let l:mname = wiki#date#get_month_name(month) - let l:mname = toupper(strcharpart(mname, 0, 1)) . strcharpart(mname, 1) - put ='## ' . mname + for l:month in sort(keys(l:month_dict)) + let l:nodes = l:month_dict[l:month] + let l:mname = wiki#date#get_month_name(l:month) + let l:mname = toupper(strcharpart(l:mname, 0, 1)) . strcharpart(l:mname, 1) + put ='## ' . l:mname put ='' - for entry in entries - let l:target = l:prefix . entry + for l:node in l:nodes + let l:target = l:prefix . l:node if !empty(b:wiki.link_extension) let l:target .= b:wiki.link_extension endif - put =wiki#link#template(l:target, entry) + put =wiki#link#template(l:target, l:node) endfor put ='' endfor @@ -107,61 +122,132 @@ endfunction " }}}1 -function! s:get_next_entry() abort " {{{1 - let l:current = expand('%:t:r') - - for [l:freq, l:fmt] in items(g:wiki_journal.date_format) - let l:rx = wiki#date#format_to_regex(l:fmt) - if l:current =~# l:rx - let l:date_dict = wiki#date#parse_format(l:current, l:fmt) - let l:date = printf('%4d-%2d-%2d', - \ l:date_dict.year, l:date_dict.month, l:date_dict.day) - let l:next = wiki#date#offset(l:date, { - \ 'daily' : '1 day', - \ 'weekly' : '1 week', - \ 'monthly' : '1 month', - \}[l:freq]) - return wiki#date#format(l:next, l:fmt) - endif - endfor - throw printf('Error: %s was not matched by any date formats', l:current) +" Functions to convert between journal nodes and date strings. Journal notes +" are formatted according to g:wiki_journal.date_format. Similarly, the date +" strings must be formatted according to one of the following types of journal +" frequencies: +" * daily: YYYY-MM-DD +" * weekly: YYYY-wWW +" * monthly: YYYY-MM + +let s:date_format = { + \ 'daily': '%Y-%m-%d', + \ 'weekly': '%Y-w%V', + \ 'monthly': '%Y-%m', + \} + +function! wiki#journal#date_to_node(...) abort " {{{1 + let l:date = a:0 > 0 + \ ? a:1 + \ : strftime(s:date_format[g:wiki_journal.frequency]) + + let l:frq = get({ + \ 10: 'daily', + \ 8: 'weekly', + \ 7: 'monthly', + \}, strlen(l:date), '') + if empty(l:frq) | return [0, ''] | endif + + let l:timestamp = wiki#date#strptime(s:date_format[l:frq], l:date) + let l:node = l:timestamp > 0 + \ ? strftime(get(g:wiki_journal.date_format, l:frq), l:timestamp) + \ : '' + + return [l:node, l:frq] endfunction " }}}1 +function! wiki#journal#get_next_date(date, frq) abort " {{{1 + let l:fmt = s:date_format[a:frq] + let l:interval = get({ + \ 'daily': 1, + \ 'weekly': 7, + \ 'monthly': 31 + \}, a:frq, 0) + + let l:timestamp = wiki#date#strptime(l:fmt, a:date) + let l:timestamp += l:interval*86400 + return strftime(l:fmt, l:timestamp) +endfunction -function! s:get_links() abort " {{{1 - let l:current = expand('%:t:r') +" }}}1 - for l:fmt in values(g:wiki_journal.date_format) - let l:rx = wiki#date#format_to_regex(l:fmt) - if l:current =~# l:rx - return s:get_links_generic(l:rx, l:fmt) - endif - endfor +function! wiki#journal#node_to_timestamp(...) abort " {{{1 + let l:node = a:0 > 0 + \ ? a:1 + \ : wiki#journal#get_current_node() + if empty(l:node) | return [0, ''] | endif + + let l:frq = wiki#journal#get_node_frq(l:node) + if empty(l:frq) | return [0, ''] | endif - return [] + return [wiki#date#strptime(g:wiki_journal.date_format[l:frq], l:node), l:frq] endfunction " }}}1 -function! s:get_links_generic(rx, fmt) abort " {{{1 - let l:globpat = wiki#paths#s(printf('%s/*.%s', - \ b:wiki.root_journal, b:wiki.extension)) - let l:links = filter(map(glob(l:globpat, 0, 1), - \ 'fnamemodify(v:val, '':t:r'')'), - \ 'v:val =~# a:rx') - - for l:cand in [ - \ strftime(a:fmt), - \ expand('%:t:r'), - \] - if l:cand =~# a:rx && index(l:links, l:cand) == -1 - call add(l:links, l:cand) - let l:sort = 1 +function! wiki#journal#node_to_date(...) abort " {{{1 + let [l:timestamp, l:frq] = call('wiki#journal#node_to_timestamp', a:000) + + let l:date = l:timestamp > 0 + \ ? strftime(s:date_format[l:frq], l:timestamp) + \ : '' + + return [l:date, l:frq] +endfunction + +" }}}1 +function! wiki#journal#get_current_node() abort " {{{1 + if !exists('b:wiki.root_journal') + return '' + endif + + return s:path_to_node(expand('%:p')) +endfunction + +" }}}1 +function! wiki#journal#get_node_frq(node) abort " {{{1 + for [l:frq, l:fmt] in items(g:wiki_journal.date_format) + if a:node =~# wiki#date#format_to_regex(l:fmt) + return l:frq endif endfor - return get(l:, 'sort', 0) ? sort(l:links) : l:links + return '' +endfunction + +" }}}1 +function! wiki#journal#get_all_nodes(frq, ...) abort " {{{1 + let l:rx = wiki#date#format_to_regex(g:wiki_journal.date_format[a:frq]) + + let l:nodes = filter(map( + \ glob(wiki#paths#s(b:wiki.root_journal . '/**'), 1, 1), + \ { _, x -> s:path_to_node(x) }), + \ { _, x -> x =~# l:rx }) + + return a:0 > 0 + \ ? uniq(sort(a:1 + l:nodes)) + \ : l:nodes +endfunction + +" }}}1 + +function! s:node_to_path(node) abort " {{{1 + let l:root = exists('b:wiki.root_journal') + \ ? b:wiki.root_journal + \ : wiki#paths#s( + \ printf('%s/%s', wiki#get_root(), g:wiki_journal.name)) + + let l:extension = exists('b:wiki.extension') + \ ? b:wiki.extension + \ : g:wiki_filetypes[0] + + return wiki#paths#s(printf('%s/%s.%s', l:root, a:node, l:extension)) +endfunction + +" }}}1 +function! s:path_to_node(path) abort " {{{1 + return wiki#paths#relative(fnamemodify(a:path, ':r'), b:wiki.root_journal) endfunction " }}}1 diff --git a/autoload/wiki/template.vim b/autoload/wiki/template.vim index 9ccb9ef5..bbeb21b0 100644 --- a/autoload/wiki/template.vim +++ b/autoload/wiki/template.vim @@ -244,4 +244,3 @@ function! s:summary.get_entries() abort dict " {{{1 endfunction " }}}1 - diff --git a/autoload/wiki/url.vim b/autoload/wiki/url.vim index 09cc46c0..6b0eb2f7 100644 --- a/autoload/wiki/url.vim +++ b/autoload/wiki/url.vim @@ -6,7 +6,7 @@ function! wiki#url#parse(url, ...) abort " {{{1 " This function parses a URL and returns a URL handler. - " + " The URL scheme specifies the desired handler with a generic handler as " a fallback. The handler is created with the following input: " @@ -16,28 +16,14 @@ function! wiki#url#parse(url, ...) abort " {{{1 " 'stripped': The url without the preceding scheme " 'url': The full url " } - " + let l:url = s:parse_url(a:url, a:0 > 0 ? a:1 : expand('%:p')) + " A handler is a dictionary object: " " handler = { " 'follow': Method to follow the url " '...': Necessary state vars " } - - let l:url = {} - let l:url.url = a:url - let l:url.origin = a:0 > 0 ? a:1 : expand('%:p') - - " Decompose the url into its scheme and stripped url - let l:parts = matchlist(a:url, '\v((\w+):%(//)?)?(.*)') - let l:url.stripped = l:parts[3] - if empty(l:parts[2]) - let l:url.scheme = 'wiki' - let l:url.url = l:url.scheme . ':' . a:url - else - let l:url.scheme = tolower(l:parts[2]) - endif - try let l:handler = wiki#url#{l:url.scheme}#handler(l:url) catch /E117:/ @@ -55,3 +41,23 @@ function! wiki#url#extend(link) abort " {{{1 endfunction " }}}1 + +function! s:parse_url(url, origin) abort " {{{1 + let l:url = {} + let l:url.url = a:url + let l:url.origin = a:origin + + " Decompose the url into its scheme and stripped url + let l:parts = matchlist(l:url.url, '\v((\w+):%(//)?)?(.*)') + let l:url.stripped = l:parts[3] + if empty(l:parts[2]) + let l:url.scheme = 'wiki' + let l:url.url = l:url.scheme . ':' . l:url.url + else + let l:url.scheme = tolower(l:parts[2]) + endif + + return l:url +endfunction + +" }}}1 diff --git a/autoload/wiki/url/journal.vim b/autoload/wiki/url/journal.vim index e465acdb..25f210b8 100644 --- a/autoload/wiki/url/journal.vim +++ b/autoload/wiki/url/journal.vim @@ -5,8 +5,24 @@ " function! wiki#url#journal#handler(url) abort " {{{1 - let a:url.stripped = printf('/%s/%s', g:wiki_journal.name, a:url.stripped) + let [l:path, l:frq] = wiki#journal#date_to_node(a:url.stripped) + + if empty(l:path) + let l:handler = deepcopy(s:dummy_handler) + let l:handler.date = a:url.stripped + return l:handler + endif + + let a:url.stripped = printf('/%s/%s', g:wiki_journal.name, l:path) return wiki#url#wiki#handler(a:url) endfunction " }}}1 + + +let s:dummy_handler = { + \ 'date': 'N/A' + \} +function! s:dummy_handler.follow(...) abort dict + call wiki#log#warn('Could not parse the journal date: ' . self.date) +endfunction diff --git a/doc/wiki.txt b/doc/wiki.txt index 788b3818..be638061 100644 --- a/doc/wiki.txt +++ b/doc/wiki.txt @@ -407,17 +407,6 @@ OPTIONS *wiki-config-options* Default value: 1 -*g:wiki_date_exe* - Specify the executable for `date`, which is used "behind the scenes" to - manipulate and calculate dates for the journal features. - - On MacOS/OSX, one can install `coreutils` from Homebrew and then use the - `gdate` program with: > - - let g:wiki_date_exe = 'gdate' -< - Default: 'date' - *g:wiki_export* A dictionary that specifies the default configuration for |WikiExport| options. See the specifications for the options for more information. @@ -498,10 +487,11 @@ OPTIONS *wiki-config-options* are: name~ - Name of journal (used to set journal path). + Name of journal. This is used to set the journal path. frequency~ - One of 'daily', 'weekly', or 'monthly'. + One of 'daily', 'weekly', or 'monthly'. Specifies the desired + journalling frequency. This is relevant for e.g. |WikiJournal|. date_format~ Dictionary of filename formats for the 'daily', 'weekly', and 'monthly' @@ -513,11 +503,41 @@ OPTIONS *wiki-config-options* %d day of month (01..31) %V ISO week number with Monday as first day of week %U week number with Sunday as first day of week + / Path delimiter + + The files will be rooted at the journal root. index_use_journal_scheme~ Whether to use the `journal:` scheme when creating journal index with |WikiJournalIndex|. + The following example specifies to create journal entries in a path + hierarchy: > + + let g:wiki_journal = { + \ 'date_format': { + \ 'daily' : '%Y/%m/%d', + \ 'weekly' : '%Y/week_%V', + \ 'monthly' : '%Y/%m/summary', + \ }, + \} +< + With this setting, the journal files would be structured something like + this: > + + wiki/journal + ├── 2021 + │ ├── week_01.wiki + │ ├── week_02.wiki + │ └── 04 + │ ├── summary.wiki + │ ├── 25.wiki + │ └── 26.wiki + └── 2022 + └── 06 + ├── 25.wiki + └── 26.wiki +< Default: > let g:wiki_journal = { @@ -997,7 +1017,9 @@ the commands are also available as mappings of the form `(wiki-[name])`. *(wiki-journal)* *WikiJournal* - Go to today's journal entry. + Go to the current journal entry. For the default journal frequency, this + means today's entry. See also |g:wiki_journal| and the `frequency` + description. *(wiki-reload)* *WikiReload* @@ -1080,7 +1102,7 @@ the commands are also available as mappings of the form `(wiki-[name])`. The following rules apply when converting text to a link. - If we are inside the journal (see |g:wiki_journal|):~ + If we are inside the journal:~ We would generally not want anything other than journal entries within the journal directory. Therefore we have some special rules here: @@ -1140,16 +1162,17 @@ the commands are also available as mappings of the form `(wiki-[name])`. *(wiki-journal-next)* *WikiJournalNext* - Go to next day/week/month. + Go to the next journal entry (at current level of daily, weekly, monthly). *(wiki-journal-prev)* *WikiJournalPrev* - Go to previous day/week/month. + Go to the previous journal entry (at current level of daily, weekly, + monthly). *(wiki-journal-copy-tonext)* *WikiJournalCopyToNext* - Copy current entry to next work day (unless the entry for next workday - already exists). + Copy current entry to the next (unless that entry already exists). Also + opens the next entry. *(wiki-journal-toweek)* *WikiJournalToWeek* @@ -1412,11 +1435,10 @@ then the `wiki` scheme is generally assumed (unless otherwise stated). The following schemes are supported: wiki~ - journal~ - Links to internal wiki pages. The address can be both relative and - absolute. Absolute addresses are relative to the wiki root. Further, the - addresses may include anchors to specific sections of the wiki entry. - Some examples might be enlightening: > + Links to internal wiki pages. The address can be both relative and + absolute. Absolute addresses are relative to the wiki root. Further, the + addresses may include anchors to specific sections of the wiki entry. Some + examples might be enlightening: > wiki:index @@ -1424,12 +1446,26 @@ The following schemes are supported: instance: [[wiki:index#section 1]] +< + journal~ + Links to journal pages. The address should be a date format at a specified + level/frequency: - Also, to link to a journal entry, one may use: - journal:2014-03-02 + * daily: `YYYY-MM-DD` (standard ISO date) + * weekly: `YYYY-wWW` + * monthly: `YYYY-MM` + + Notice that these formats will be translated to the corresponding filename + formats in the `date_format` key of |g:wiki_journal|. Some examples of + journal urls: > - For convenience, an ISO date will also work as a journal link. + journal:2014-03-02 + journal:2014-w09 + journal:2014-03 < + For convenience, the ISO date will also be recognized as a journal url + without the scheme. + file~ Links to a local file. |g:wiki_viewer| is used by default to open a file. For more user flexibility, one can implement a custom file handler with diff --git a/plugin/wiki.vim b/plugin/wiki.vim index dd7b55d9..8e4132de 100644 --- a/plugin/wiki.vim +++ b/plugin/wiki.vim @@ -83,7 +83,7 @@ command! WikiEnable call wiki#buffer#init() command! WikiIndex call wiki#goto_index() command! WikiOpen call wiki#page#open() command! WikiReload call wiki#reload() -command! WikiJournal call wiki#journal#make_note() +command! WikiJournal call wiki#journal#open() command! CtrlPWiki call ctrlp#init(ctrlp#wiki#id()) command! WikiFzfPages call wiki#fzf#pages() command! WikiFzfTags call wiki#fzf#tags() diff --git a/test/test-journal/test-basics.vim b/test/test-journal/test-basics.vim index 5797b728..8de0839b 100644 --- a/test/test-journal/test-basics.vim +++ b/test/test-journal/test-basics.vim @@ -5,24 +5,22 @@ let g:wiki_journal = {'name' : 'diary'} runtime plugin/wiki.vim bwipeout! -silent call wiki#journal#make_note() +silent call wiki#journal#open() call assert_true(b:wiki.in_journal) bwipeout! let g:wiki_journal.date_format.daily = '%d.%m.%Y' let s:date = strftime(g:wiki_journal.date_format[g:wiki_journal.frequency]) -silent call wiki#journal#make_note() +silent call wiki#journal#open() call assert_equal(s:date, expand('%:t:r')) -let s:step = index(sort(['01.02.2019', s:date]), s:date) - \ ? -1 : 1 -silent call wiki#journal#go(s:step) +silent call wiki#journal#go(-1) call assert_equal('01.02.2019', expand('%:t:r')) silent bwipeout! let g:wiki_journal.frequency = 'weekly' let s:date = strftime(g:wiki_journal.date_format[g:wiki_journal.frequency]) -silent call wiki#journal#make_note() +silent call wiki#journal#open() call assert_equal(expand('%:t:r'), s:date) silent call wiki#journal#go(-1) call assert_equal(expand('%:t:r'), '2019_w02') @@ -30,7 +28,7 @@ call assert_equal(expand('%:t:r'), '2019_w02') silent bwipeout! let g:wiki_journal.frequency = 'monthly' let s:date = strftime(g:wiki_journal.date_format[g:wiki_journal.frequency]) -silent call wiki#journal#make_note() +silent call wiki#journal#open() call assert_equal(expand('%:t:r'), s:date) silent call wiki#journal#go(-1) call assert_equal(expand('%:t:r'), '2019_m02') diff --git a/test/test-journal/test-dates.vim b/test/test-journal/test-dates.vim new file mode 100644 index 00000000..41f46e85 --- /dev/null +++ b/test/test-journal/test-dates.vim @@ -0,0 +1,33 @@ +source ../init.vim +let g:wiki_root = g:testroot . '/wiki-basic' +runtime plugin/wiki.vim + +let g:wiki_journal.date_format = { + \ 'daily': '%d.%m.%Y', + \ 'weekly': '%Y/w%V', + \ 'monthly': '%Y/m%m', + \} + + +call assert_equal(['15.10.2022', 'daily'], wiki#journal#date_to_node('2022-10-15')) +call assert_equal(['2022/w40', 'weekly'], wiki#journal#date_to_node('2022-w40')) +call assert_equal(['2022/m10', 'monthly'], wiki#journal#date_to_node('2022-10')) + +call assert_equal(['2022-10-15', 'daily'], wiki#journal#node_to_date('15.10.2022')) +call assert_equal(['2022-w40', 'weekly'], wiki#journal#node_to_date('2022/w40')) +call assert_equal(['2022-10', 'monthly'], wiki#journal#node_to_date('2022/m10')) +call assert_equal(['', ''], wiki#journal#node_to_date()) +silent call wiki#journal#open() +call assert_equal([strftime('%F'), 'daily'], wiki#journal#node_to_date()) +bwipeout! + +call assert_equal('2022-10-01', wiki#journal#get_next_date('2022-09-30', 'daily')) +call assert_equal('2023-01-01', wiki#journal#get_next_date('2022-12-31', 'daily')) +call assert_equal('2022-w52', wiki#journal#get_next_date('2022-w51', 'weekly')) +call assert_equal('2023-w01', wiki#journal#get_next_date('2022-w52', 'weekly')) +call assert_equal('2020-w53', wiki#journal#get_next_date('2020-w52', 'weekly')) +call assert_equal('2023-01', wiki#journal#get_next_date('2022-12', 'monthly')) +call assert_equal('2020-05', wiki#journal#get_next_date('2020-04', 'monthly')) + + +call wiki#test#finished() diff --git a/test/test-templates/test.vim b/test/test-templates/test.vim index f67442f8..860bc4a0 100644 --- a/test/test-templates/test.vim +++ b/test/test-templates/test.vim @@ -44,7 +44,7 @@ runtime plugin/wiki.vim silent call wiki#page#open('Template A') call assert_equal([ \ '# TEMPLATE A', - \ 'Created: ' . strftime("%F") . ' ' . strftime("%H:%M"), + \ 'Created: ' . strftime("%F %H:%M"), \ '', \ 'TEST', \], getline(1, line('$') - 1))