Skip to content

Commit a12c410

Browse files
committed
Merge pull request #28 from lifepillar/reports
Reports and reconciling
2 parents 1ecfd05 + 4499745 commit a12c410

File tree

4 files changed

+501
-7
lines changed

4 files changed

+501
-7
lines changed

autoload/ledger.vim

Lines changed: 230 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
" vim:ts=2:sw=2:sts=2:foldmethod=marker
12
function! ledger#transaction_state_toggle(lnum, ...)
23
if a:0 == 1
34
let chars = a:1
@@ -398,5 +399,233 @@ func! ledger#entry()
398399
let l = line('.') - 1 " Insert transaction at the current line (i.e., below the line above the current one)
399400
let query = getline('.')
400401
normal "_dd
401-
exec l . 'read !' g:ledger_bin '-f' shellescape(expand('%')) 'entry' shellescape(query)
402+
exec l . 'read !' g:ledger_bin '-f' shellescape(expand(g:ledger_main)) 'entry' shellescape(query)
402403
endfunc
404+
405+
" Report generation {{{1
406+
407+
" Helper functions and variables {{{2
408+
" Position of report windows
409+
let s:winpos_map = {
410+
\ "T": "to new", "t": "abo new", "B": "bo new", "b": "bel new",
411+
\ "L": "to vnew", "l": "abo vnew", "R": "bo vnew", "r": "bel vnew"
412+
\ }
413+
414+
function! s:error_message(msg)
415+
redraw " See h:echo-redraw
416+
echohl ErrorMsg
417+
echo "\r"
418+
echomsg a:msg
419+
echohl NONE
420+
endf
421+
422+
function! s:warning_message(msg)
423+
redraw " See h:echo-redraw
424+
echohl WarningMsg
425+
echo "\r"
426+
echomsg a:msg
427+
echohl NONE
428+
endf
429+
430+
" Open the quickfix/location window when it is not empty,
431+
" closes it if it is empty.
432+
"
433+
" Optional parameters:
434+
" a:1 Quickfix window title.
435+
" a:2 Message to show when the window is empty.
436+
"
437+
" Returns 0 if the quickfix window is empty, 1 otherwise.
438+
function! s:quickfix_toggle(...)
439+
if g:ledger_use_location_list
440+
let l:list = 'l'
441+
let l:open = (len(getloclist(winnr())) > 0)
442+
else
443+
let l:list = 'c'
444+
let l:open = (len(getqflist()) > 0)
445+
endif
446+
447+
if l:open
448+
execute (g:ledger_qf_vertical ? 'vert' : 'botright') l:list.'open' g:ledger_qf_size
449+
" Set local mappings to quit the quickfix window or lose focus.
450+
nnoremap <silent> <buffer> <tab> <c-w><c-w>
451+
execute 'nnoremap <silent> <buffer> q :' l:list.'close<CR>'
452+
" Note that the following settings do not persist (e.g., when you close and re-open the quickfix window).
453+
" See: http://superuser.com/questions/356912/how-do-i-change-the-quickix-title-status-bar-in-vim
454+
if g:ledger_qf_hide_file
455+
setl conceallevel=2
456+
setl concealcursor=nc
457+
syntax match qfFile /^[^|]*/ transparent conceal
458+
endif
459+
if a:0 > 0
460+
let w:quickfix_title = a:1
461+
endif
462+
return 1
463+
endif
464+
465+
execute l:list.'close'
466+
call s:warning_message((a:0 > 1) ? a:2 : 'No results')
467+
return 0
468+
endf
469+
470+
" Populate a quickfix/location window with data. The argument must be a String
471+
" or a List.
472+
function! s:quickfix_populate(data)
473+
" Note that cexpr/lexpr always uses the global value of errorformat
474+
let l:efm = &errorformat " Save global errorformat
475+
set errorformat=%EWhile\ parsing\ file\ \"%f\"\\,\ line\ %l:,%ZError:\ %m,%-C%.%#
476+
set errorformat+=%tarning:\ \"%f\"\\,\ line\ %l:\ %m
477+
" Format to parse command-line errors:
478+
set errorformat+=Error:\ %m
479+
" Format to parse reports:
480+
set errorformat+=%f:%l\ %m
481+
set errorformat+=%-G%.%#
482+
execute (g:ledger_use_location_list ? 'l' : 'c').'getexpr' 'a:data'
483+
let &errorformat = l:efm " Restore global errorformat
484+
return
485+
endf
486+
487+
" Build a ledger command to process the given file.
488+
function! s:ledger_cmd(file, args)
489+
return join([g:ledger_bin, g:ledger_extra_options, '-f', shellescape(expand(a:file)), a:args])
490+
endf
491+
" }}}
492+
493+
" Run an arbitrary ledger command and show the output in a new buffer. If
494+
" there are errors, no new buffer is opened: the errors are displayed in a
495+
" quickfix window instead.
496+
"
497+
" Parameters:
498+
" file The file to be processed.
499+
" args A string of Ledger command-line arguments.
500+
function! ledger#report(file, args)
501+
let l:output = systemlist(s:ledger_cmd(a:file, a:args))
502+
if v:shell_error " If there are errors, show them in a quickfix/location list.
503+
call s:quickfix_populate(l:output)
504+
call s:quickfix_toggle('Errors', 'Unable to parse errors')
505+
return
506+
endif
507+
if empty(l:output)
508+
call s:warning_message('No results')
509+
return
510+
endif
511+
" Open a new buffer to show Ledger's output.
512+
execute get(s:winpos_map, g:ledger_winpos, "bo new")
513+
setlocal buftype=nofile bufhidden=wipe nobuflisted noswapfile nowrap
514+
call append(0, l:output)
515+
setlocal nomodifiable
516+
" Set local mappings to quit window or lose focus.
517+
nnoremap <silent> <buffer> <tab> <c-w><c-w>
518+
nnoremap <silent> <buffer> q <c-w>c
519+
" Add some coloring to the report
520+
syntax match LedgerNumber /-\@1<!\d\+\([,.]\d\+\)\+/
521+
syntax match LedgerNegativeNumber /-\d\+\([,.]\d\+\)\+/
522+
syntax match LedgerImproperPerc /\d\d\d\+%/
523+
endf
524+
525+
" Show an arbitrary register report in a quickfix list.
526+
"
527+
" Parameters:
528+
" file The file to be processed
529+
" args A string of Ledger command-line arguments.
530+
function! ledger#register(file, args)
531+
let l:cmd = s:ledger_cmd(a:file, join([
532+
\ "register",
533+
\ "--format='" . g:ledger_qf_register_format . "'",
534+
\ "--prepend-format='%(filename):%(beg_line) '",
535+
\ a:args
536+
\ ]))
537+
call s:quickfix_populate(systemlist(l:cmd))
538+
call s:quickfix_toggle('Register report')
539+
endf
540+
541+
" Reconcile the given account.
542+
" This function accepts a file path as a third optional argument.
543+
" The default is to use the value of g:ledger_main.
544+
"
545+
" Parameters:
546+
" file The file to be processed
547+
" account An account name (String)
548+
" target_amount The target amount (Float)
549+
function! ledger#reconcile(file, account, target_amount)
550+
let l:cmd = s:ledger_cmd(a:file, join([
551+
\ "register",
552+
\ "--uncleared",
553+
\ "--format='" . g:ledger_qf_reconcile_format . "'",
554+
\ "--prepend-format='%(filename):%(beg_line) %(pending ? \"P\" : \"U\") '",
555+
\ a:account
556+
\ ]))
557+
let l:file = expand(a:file) " Needed for #show_balance() later
558+
call s:quickfix_populate(systemlist(l:cmd))
559+
if s:quickfix_toggle("Reconcile " . a:account, "Nothing to reconcile")
560+
let g:ledger_target_amount = a:target_amount
561+
" Show updated account balance upon saving, as long as the quickfix window is open
562+
augroup reconcile
563+
autocmd!
564+
execute "autocmd BufWritePost *.ldg,*.ledger call ledger#show_balance('" . l:file . "','" . a:account . "')"
565+
autocmd BufWipeout <buffer> call <sid>finish_reconciling()
566+
augroup END
567+
" Add refresh shortcut
568+
execute "nnoremap <silent> <buffer> <c-l> :<c-u>call ledger#reconcile('"
569+
\ . l:file . "','" . a:account . "'," . string(a:target_amount) . ")<cr>"
570+
call ledger#show_balance(l:file, a:account)
571+
endif
572+
endf
573+
574+
function! s:finish_reconciling()
575+
unlet g:ledger_target_amount
576+
augroup reconcile
577+
autocmd!
578+
augroup END
579+
augroup! reconcile
580+
endf
581+
582+
" Show the pending/cleared balance of an account.
583+
" This function has an optional parameter:
584+
"
585+
" a:1 An account name
586+
"
587+
" If no account if given, the account in the current line is used.
588+
function! ledger#show_balance(file, ...)
589+
let l:account = a:0 > 0 && !empty(a:1) ? a:1 : matchstr(getline('.'), '\m\( \|\t\)\zs\S.\{-}\ze\( \|\t\|$\)')
590+
if empty(l:account)
591+
call s:error_message('No account found')
592+
return
593+
endif
594+
let l:cmd = s:ledger_cmd(a:file, join([
595+
\ "cleared",
596+
\ l:account,
597+
\ "--empty",
598+
\ "--collapse",
599+
\ "--format='%(scrub(get_at(display_total, 0)))|%(scrub(get_at(display_total, 1)))|%(quantity(scrub(get_at(display_total, 1))))'",
600+
\ (empty(g:ledger_default_commodity) ? '' : "-X " . shellescape(g:ledger_default_commodity))
601+
\ ]))
602+
let l:output = systemlist(l:cmd)
603+
" Errors may occur, for example, when the account has multiple commodities
604+
" and g:ledger_default_commodity is empty.
605+
if v:shell_error
606+
call s:quickfix_populate(l:output)
607+
call s:quickfix_toggle('Errors', 'Unable to parse errors')
608+
return
609+
endif
610+
let l:amounts = split(l:output[-1], '|')
611+
redraw " Necessary in some cases to overwrite previous messages. See :h echo-redraw
612+
if len(l:amounts) < 3
613+
call s:error_message("Could not determine balance. Did you use a valid account?")
614+
return
615+
endif
616+
echo g:ledger_pending_string
617+
echohl LedgerPending
618+
echon l:amounts[0]
619+
echohl NONE
620+
echon ' ' g:ledger_cleared_string
621+
echohl LedgerCleared
622+
echon l:amounts[1]
623+
echohl NONE
624+
if exists('g:ledger_target_amount')
625+
echon ' ' g:ledger_target_string
626+
echohl LedgerTarget
627+
echon printf('%.2f', (g:ledger_target_amount - str2float(l:amounts[2])))
628+
echohl NONE
629+
endif
630+
endf
631+
" }}}

compiler/ledger.vim

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ if exists(":CompilerSet") != 2
1313
endif
1414

1515
" default value will be set in ftplugin
16-
if ! exists("g:ledger_bin") || empty(g:ledger_bin) || ! executable(split(g:ledger_bin, '\s')[0])
16+
if ! exists("g:ledger_bin") || empty(g:ledger_bin) || ! executable(g:ledger_bin)
1717
finish
1818
endif
1919

@@ -25,5 +25,5 @@ CompilerSet errorformat+=%tarning:\ \"%f\"\\,\ line\ %l:\ %m
2525
CompilerSet errorformat+=%-G%.%#
2626

2727
" Check file syntax
28-
exe 'CompilerSet makeprg='.substitute(g:ledger_bin, ' ', '\\ ', 'g').'\ source\ %:S'
28+
exe 'CompilerSet makeprg='.substitute(g:ledger_bin, ' ', '\\ ', 'g').'\ '.substitute(g:ledger_extra_options, ' ', '\\ ', 'g').'\ source\ %:S'
2929

0 commit comments

Comments
 (0)