Skip to content

Commit 086a84b

Browse files
committed
code cleanup
My intention is to facilitate calling of transaction parsing functions from outside the ftplugin script thus I moved to implementation to an autoload file.
1 parent 5c30f4a commit 086a84b

File tree

2 files changed

+350
-336
lines changed

2 files changed

+350
-336
lines changed

autoload/ledger.vim

Lines changed: 332 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,332 @@
1+
function! ledger#transaction_state_toggle(lnum, ...)
2+
if a:0 == 1
3+
let chars = a:1
4+
else
5+
let chars = ' *'
6+
endif
7+
let trans = s:transaction.from_lnum(a:lnum)
8+
if empty(trans) || has_key(trans, 'expr')
9+
return
10+
endif
11+
12+
let old = has_key(trans, 'state') ? trans['state'] : ' '
13+
let i = stridx(chars, old) + 1
14+
let new = chars[i >= len(chars) ? 0 : i]
15+
16+
call trans.set_state(new)
17+
18+
call setline(trans['head'], trans.format_head())
19+
endf
20+
21+
function! ledger#transaction_state_set(lnum, char)
22+
" modifies or sets the state of the transaction at the cursor,
23+
" removing the state alltogether if a:char is empty
24+
let trans = s:transaction.from_lnum(a:lnum)
25+
if empty(trans) || has_key(trans, 'expr')
26+
return
27+
endif
28+
29+
call trans.set_state(a:char)
30+
31+
call setline(trans['head'], trans.format_head())
32+
endf
33+
34+
function! ledger#transaction_date_set(lnum, type, ...) "{{{1
35+
let time = a:0 == 1 ? a:1 : localtime()
36+
let trans = s:transaction.from_lnum(a:lnum)
37+
if empty(trans) || has_key(trans, 'expr')
38+
return
39+
endif
40+
41+
let formatted = strftime('%Y/%m/%d', time)
42+
if has_key(trans, 'date') && ! empty(trans['date'])
43+
let date = split(trans['date'], '=')
44+
else
45+
let date = [formatted]
46+
endif
47+
48+
if a:type =~? 'effective\|actual'
49+
echoerr "actual/effective arguments were replaced by primary/auxiliary"
50+
return
51+
endif
52+
53+
if a:type ==? 'primary'
54+
let date[0] = formatted
55+
elseif a:type ==? 'auxiliary'
56+
if time < 0
57+
" remove auxiliary date
58+
let date = [date[0]]
59+
else
60+
" set auxiliary date
61+
if len(date) >= 2
62+
let date[1] = formatted
63+
else
64+
call add(date, formatted)
65+
endif
66+
endif
67+
elseif a:type ==? 'unshift'
68+
let date = [formatted, date[0]]
69+
endif
70+
71+
let trans['date'] = join(date[0:1], '=')
72+
73+
call setline(trans['head'], trans.format_head())
74+
endf "}}}
75+
76+
" == get transactions ==
77+
78+
function! ledger#transaction_from_lnum(lnum)
79+
return s:transaction.from_lnum(a:lnum)
80+
endf
81+
82+
function! ledger#transactions(...)
83+
if a:0 == 2
84+
let lnum = a:1
85+
let end = a:2
86+
elseif a:0 == 0
87+
let lnum = 1
88+
let end = line('$')
89+
else
90+
throw "wrong number of arguments for get_transactions()"
91+
return []
92+
endif
93+
94+
" safe view / position
95+
let view = winsaveview()
96+
let fe = &foldenable
97+
set nofoldenable
98+
99+
let transactions = []
100+
call cursor(lnum, 0)
101+
while lnum && lnum <= end
102+
let trans = s:transaction.from_lnum(lnum)
103+
if ! empty(trans)
104+
call add(transactions, trans)
105+
call cursor(trans['tail'], 0)
106+
endif
107+
let lnum = search('^[~=[:digit:]]', 'cW')
108+
endw
109+
110+
" restore view / position
111+
let &foldenable = fe
112+
call winrestview(view)
113+
114+
return transactions
115+
endf
116+
117+
" == transaction object implementation ==
118+
119+
let s:transaction = {} "{{{1
120+
function! s:transaction.new() dict
121+
return copy(s:transaction)
122+
endf
123+
124+
function! s:transaction.from_lnum(lnum) dict "{{{2
125+
let [head, tail] = s:get_transaction_extents(a:lnum)
126+
if ! head
127+
return {}
128+
endif
129+
130+
let trans = copy(s:transaction)
131+
let trans['head'] = head
132+
let trans['tail'] = tail
133+
134+
" split off eventual comments at the end of line
135+
let line = split(getline(head), '\ze\s*\%(\t\| \);', 1)
136+
if len(line) > 1
137+
let trans['appendix'] = join(line[1:], '')
138+
endif
139+
140+
" parse rest of line
141+
" FIXME (minor): will not preserve spacing (see 'join(parts)')
142+
let parts = split(line[0], '\s\+')
143+
if parts[0] ==# '~'
144+
let trans['expr'] = join(parts[1:])
145+
return trans
146+
elseif parts[0] ==# '='
147+
let trans['auto'] = join(parts[1:])
148+
return trans
149+
elseif parts[0] !~ '^\d'
150+
" this case is avoided in s:get_transaction_extents(),
151+
" but we'll check anyway.
152+
return {}
153+
endif
154+
155+
for part in parts
156+
if ! has_key(trans, 'date') && part =~ '^\d'
157+
let trans['date'] = part
158+
elseif ! has_key(trans, 'code') && part =~ '^([^)]*)$'
159+
let trans['code'] = part[1:-2]
160+
elseif ! has_key(trans, 'state') && part =~ '^[[:punct:]]$'
161+
" the first character by itself is assumed to be the state of the transaction.
162+
let trans['state'] = part
163+
else
164+
" everything after date/code or state belongs to the description
165+
break
166+
endif
167+
call remove(parts, 0)
168+
endfor
169+
170+
let trans['description'] = join(parts)
171+
return trans
172+
endf "}}}
173+
174+
function! s:transaction.set_state(char) dict "{{{2
175+
if has_key(self, 'state') && a:char =~ '^\s*$'
176+
call remove(self, 'state')
177+
else
178+
let self['state'] = a:char
179+
endif
180+
endf "}}}
181+
182+
function! s:transaction.parse_body(...) dict "{{{2
183+
if a:0 == 2
184+
let head = a:1
185+
let tail = a:2
186+
elseif a:0 == 0
187+
let head = self['head']
188+
let tail = self['tail']
189+
else
190+
throw "wrong number of arguments for parse_body()"
191+
return []
192+
endif
193+
194+
if ! head || tail <= head
195+
return []
196+
endif
197+
198+
let lnum = head
199+
let tags = {}
200+
let postings = []
201+
while lnum <= tail
202+
let line = split(getline(lnum), '\s*\%(\t\| \);', 1)
203+
204+
if line[0] =~ '^\s\+[^[:blank:];]'
205+
" posting
206+
let [state, rest] = matchlist(line[0], '^\s\+\([*!]\?\)\s*\(.*\)$')[1:2]
207+
if rest =~ '\t\| '
208+
let [account, amount] = matchlist(rest, '^\(.\{-}\)\%(\t\| \)\s*\(.\{-}\)\s*$')[1:2]
209+
else
210+
let amount = ''
211+
let account = matchstr(rest, '^\s*\zs.\{-}\ze\s*$')
212+
endif
213+
call add(postings, {'account': account, 'amount': amount, 'state': state})
214+
end
215+
216+
" where are tags to be stored?
217+
if empty(postings)
218+
" they belong to the transaction
219+
let tag_container = tags
220+
else
221+
" they belong to last posting
222+
if ! has_key(postings[-1], 'tags')
223+
let postings[-1]['tags'] = {}
224+
endif
225+
let tag_container = postings[-1]['tags']
226+
endif
227+
228+
let comment = join(line[1:], ' ;')
229+
if comment =~ '^\s*:'
230+
" tags without values
231+
for t in s:findall(comment, ':\zs[^:[:blank:]]\([^:]*[^:[:blank:]]\)\?\ze:')
232+
let tag_container[t] = ''
233+
endfor
234+
elseif comment =~ '^\s*[^:[:blank:]][^:]\+:'
235+
" tag with value
236+
let key = matchstr(comment, '^\s*\zs[^:]\+\ze:')
237+
if ! empty(key)
238+
let val = matchstr(comment, ':\s*\zs.*\ze\s*$')
239+
let tag_container[key] = val
240+
endif
241+
endif
242+
let lnum += 1
243+
endw
244+
return [tags, postings]
245+
endf "}}}
246+
247+
function! s:transaction.format_head() dict "{{{2
248+
if has_key(self, 'expr')
249+
return '~ '.self['expr']
250+
elseif has_key(self, 'auto')
251+
return '= '.self['auto']
252+
endif
253+
254+
let parts = []
255+
if has_key(self, 'date') | call add(parts, self['date']) | endif
256+
if has_key(self, 'code') | call add(parts, '('.self['code'].')') | endif
257+
if has_key(self, 'state') | call add(parts, self['state']) | endif
258+
if has_key(self, 'description') | call add(parts, self['description']) | endif
259+
260+
let line = join(parts)
261+
if has_key(self, 'appendix') | let line .= self['appendix'] | endif
262+
263+
return line
264+
endf "}}}
265+
"}}}
266+
267+
" == helper functions ==
268+
269+
function! s:get_transaction_extents(lnum)
270+
if ! (indent(a:lnum) || getline(a:lnum) =~ '^[~=[:digit:]]')
271+
" only do something if lnum is in a transaction
272+
return [0, 0]
273+
endif
274+
275+
" safe view / position
276+
let view = winsaveview()
277+
let fe = &foldenable
278+
set nofoldenable
279+
280+
call cursor(a:lnum, 0)
281+
let head = search('^[~=[:digit:]]', 'bcnW')
282+
let tail = search('^[^;[:blank:]]\S\+', 'nW')
283+
let tail = tail > head ? tail - 1 : line('$')
284+
285+
" restore view / position
286+
let &foldenable = fe
287+
call winrestview(view)
288+
289+
return head ? [head, tail] : [0, 0]
290+
endf
291+
292+
function! ledger#find_in_tree(tree, levels)
293+
if empty(a:levels)
294+
return []
295+
endif
296+
let results = []
297+
let currentlvl = a:levels[0]
298+
let nextlvls = a:levels[1:]
299+
let branches = ledger#filter_items(keys(a:tree), currentlvl)
300+
let exact = empty(nextlvls)
301+
for branch in branches
302+
call add(results, [branch, exact])
303+
if ! empty(nextlvls)
304+
for [result, exact] in ledger#find_in_tree(a:tree[branch], nextlvls)
305+
call add(results, [branch.':'.result, exact])
306+
endfor
307+
endif
308+
endfor
309+
return results
310+
endf
311+
312+
function! ledger#filter_items(list, keyword)
313+
" return only those items that start with a specified keyword
314+
return filter(copy(a:list), 'v:val =~ ''^\V'.substitute(a:keyword, '\\', '\\\\', 'g').'''')
315+
endf
316+
317+
function! s:findall(text, rx)
318+
" returns all the matches in a string,
319+
" there will be overlapping matches according to :help match()
320+
let matches = []
321+
322+
while 1
323+
let m = matchstr(a:text, a:rx, 0, len(matches)+1)
324+
if empty(m)
325+
break
326+
endif
327+
328+
call add(matches, m)
329+
endw
330+
331+
return matches
332+
endf

0 commit comments

Comments
 (0)