ZyX_I avatar ZyX_I committed fe35863

@aurum/drivers/subversion: Rewritten “log --xml” parser. Fixes #18

Comments (0)

Files changed (3)

macros/reload-aurum.vim

 for s:plug in ['@aurum/repo',
             \  '@aurum/bufvars',
             \  '@aurum/cache',
+            \  '@aurum/drivers/common/xml',
             \  '@aurum/drivers/common/utils',
             \  '@aurum/drivers/common/hypsites',]
     try

plugin/aurum/drivers/common/xml.vim

+"▶1
+scriptencoding utf-8
+if !exists('s:_pluginloaded')
+    execute frawor#Setup('0.0', {'@/resources': '0.0',}, 0)
+    finish
+elseif s:_pluginloaded
+    finish
+endif
+let s:xml={}
+"▶1 messages
+let s:_messages={
+            \ 'allblanks': 'No non-blank characters found',
+            \'tstartnfnd': 'Expected tag start (“<tagname”), but got “%s”',
+            \  'tendnfnd': 'Expected tag end (“>”), but got “%s”',
+            \  'ctagnfnd': 'Expected “%s”, but got “%s”',
+            \     'ctend': 'Expected “%s”, but got nothing',
+            \     'ttend': 'Expected tag end, but got nothing',
+        \}
+"▶1 new :: [String] → xml
+function s:F.new(lines)
+    let xml=deepcopy(s:xml)
+    let xml.lines=copy(a:lines)
+    let xml.tags=[]
+    return xml
+endfunction
+"▶1 skipws :: &
+function s:xml.skipws()
+    try
+        while self.lines[0]!~#'\S'
+            call remove(self.lines, 0)
+        endwhile
+        let self.lines[0]=substitute(self.lines[0], '\v^\s+', '', '')
+    catch /\m^Vim(\l*):E684:/
+        call s:_f.throw('allblanks')
+    endtry
+endfunction
+"▶1 decodeentities :: String → String
+let s:entities={
+            \  'lt': '<',
+            \  'gt': '>',
+            \ 'amp': '&',
+            \'quot': '"',
+        \}
+function s:F.decodeentities(s)
+    return substitute(a:s, '\v\&([lg]t|amp)\;', '\=s:entities[submatch(1)]','g')
+endfunction
+"▶1 skip :: &(len)
+function s:xml.skip(len)
+    let self.lines[0]=self.lines[0][(a:len):]
+    return self.skipws()
+endfunction
+"▶1 parsetag :: & → (tagname, attributes)
+" Assumes that tag names have only alphabetic characters
+function s:xml.parsetag()
+    call self.skipws()
+    let m=matchlist(self.lines[0], '\v\<(\a+)')[:1]
+    if empty(m)
+        call s:_f.throw('tstartnfnd', self.lines[0])
+    endif
+    let [match, tagname]=m
+    let self.tags+=[tagname]
+    call self.skip(len(match))
+    let attributes={}
+    while 1
+        let m=matchlist(self.lines[0], '\v^([a-zA-Z0-9_\-]+)\=\"(.{-})\"')[:2]
+        if empty(m)
+            break
+        endif
+        let [match, attrname, attrval]=m
+        call self.skip(len(match))
+        let attributes[attrname]=s:F.decodeentities(attrval)
+    endwhile
+    if self.lines[0][0] isnot# '>'
+        call s:_f.throw('tendnfnd', self.lines[0])
+    endif
+    let self.lines[0]=self.lines[0][1:]
+    return [tagname, attributes]
+endfunction
+"▶1 checkctag :: & → Bool
+function s:xml.checkctag()
+    return (!empty(self.lines) && self.lines[0][:1] is# '</')
+endfunction
+"▶1 skipctag :: &
+function s:xml.skipctag()
+    let tagname=remove(self.tags, -1)
+    let e='</'.tagname.'>'
+    let le=len(e)
+    try
+        if self.lines[0][:(le)] isnot# e
+            call s:_f.throw('ctagnfnd', e, self.lines[0])
+        endif
+        let self.lines[0]=self.lines[0][(le):]
+    catch /\m^Vim(\l*):E684:/
+        call s:_f.throw('ctend', e)
+    endtry
+endfunction
+"▶1 parsetextintag :: & → text
+function s:xml.parsetextintag()
+    try
+        let text=[]
+        while 1
+            let idx=stridx(self.lines[0], '<')
+            if idx!=-1
+                break
+            endif
+            let text+=remove(self.lines, 0, 0)
+        endwhile
+        if idx
+            let text+=[self.lines[0][:(idx-1)]]
+            let self.lines[0]=self.lines[0][(idx):]
+        else
+            let text+=['']
+        endif
+        call map(text, 's:F.decodeentities(v:val)')
+        call self.skipctag()
+        return text
+    catch /\m^Vim(\l*):E684:/
+        call s:_f.throw('ttend')
+    endtry
+endfunction
+"▶1 post resource
+call s:_f.postresource('xml', {'new': s:F.new,
+            \       'decodeentities': s:F.decodeentities})
+"▶1
+call frawor#Lockvar(s:, '_pluginloaded')
+" vim: ft=vim ts=4 sts=4 et fmr=▶,▲

plugin/aurum/drivers/subversion.vim

     execute frawor#Setup('0.1', {   '@aurum/repo': '2.3',
                 \                          '@/os': '0.0',
                 \   '@aurum/drivers/common/utils': '0.0',
+                \     '@aurum/drivers/common/xml': '0.0',
                 \'@aurum/drivers/common/hypsites': '0.0',}, 0)
     finish
 elseif s:_pluginloaded
             \  'statf': 'Failed to get status of the repository %s: %s',
             \  'nocfg': 'Failed to get property %s of repository %s',
             \'fcunsup': 'Forced copy is not supported',
-            \   'perr': 'Parser error: expected %s, but got %s',
+            \'perrtag': 'Parser error: expected tag %s, but got %s',
+            \'perratt': 'Parser error: expected attribute %s, but got only %s',
+            \ 'perrtv': 'Parser error: expected something like %s in text, '.
+            \           'but got %s',
+            \'perratv': 'Parser error: expected something like %s '.
+            \           'in %s value, but got %s',
             \   'derr': 'Program “date” failed to parse date “%s” obtained '.
             \           'by parsing “%s”: %s',
             \'infoerr': 'Failed to get information for the repository %s: '.
     return s:F.getfrominfo(a:repo, 'Revision', {'revision': ''.a:rev})
 endfunction
 "▶1 decodeentities :: String → String
-let s:entities={
-            \  'lt': '<',
-            \  'gt': '>',
-            \ 'amp': '&',
-            \'quot': '"',
-        \}
-function s:F.decodeentities(s)
-    return substitute(a:s, '\v\&([lg]t|amp)\;', '\=s:entities[submatch(1)]','g')
-endfunction
-"▶1 parsecs :: repo, csdata, lstart::UInt → (cs, line::UInt)
+let s:F.decodeentities=s:_r.xml.decodeentities
+"▶1 parsecs :: repo, xml → cs
 let s:logseparator=repeat('-', 72)
 let s:csinit={
             \'branch': 'default',
 let s:hasdateexe=executable('date')
 " TODO HEAD, ... in cs.tags
 " TODO use merge information if available
-function s:F.parsecs(repo, csdata, line)
+function s:F.parsecs(repo, xml)
     let cs=deepcopy(s:csinit)
-    let lcsdata=len(a:csdata)
-    "▶2 Check for logentry start
-    let line=a:line
-    if a:csdata[line][:8] isnot# '<logentry'
-        call s:_f.throw('perr', '<logentry', a:csdata[line])
+    "▶2 <logentry> → rev, hex; parents
+    let [tagname, attributes]=a:xml.parsetag()
+    call a:xml.skipws()
+    if tagname isnot# 'logentry'
+        call s:_f.throw('perrtag', 'logentry', tagname)
+    elseif !has_key(attributes, 'revision')
+        call s:_f.throw('perratt', 'revision', join(keys(attributes), ', '))
     endif
-    let line+=1
-    "▶2 rev and hex, parents
-    let rev=matchstr(a:csdata[line], '\v(\srevision\=\")@<=(\d+)\"@=')
-    if empty(rev)
-        call s:_f.throw('perr', 'revision="N"', a:csdata[line])
-    endif
-    let cs.rev=str2nr(rev)
-    let cs.hex=rev
+    let cs.hex=attributes.revision
+    let cs.rev=str2nr(cs.hex)
     let cs.parents=((cs.rev>1)?([''.(cs.rev-1)]):([]))
-    let line+=1
-    "▶2 author
-    let author=matchstr(a:csdata[line], '\v(\<author\>)@<=.*(\<\/author\>)@=')
-    if empty(author)
-        call s:_f.throw('perr', '<author>name</author>', a:csdata[line])
-    endif
-    let cs.user=s:F.decodeentities(author)
-    let line+=1
-    "▶2 date
-    " date :: (year, month, day, hour, minute, second)
-    if s:hasdateexe || executable('date')
-        let s:hasdateexe=1
-        let date=matchlist(a:csdata[line], '\V<date>'.
-                    \                        '\v(\d{4,})\-(\d\d)\-(\d\d)'.
-                    \                         'T(\d\d)\:(\d\d)\:(\d\d)\.\d+Z'.
-                    \                      '\V</date>')[1:6]
-        if empty(date)
-            call s:_f.throw('perr', '<date>yyyy-mm-ddTHH:MM:SS.nnnnnnZ</date>',
-                        \   a:csdata[line])
+    "▲2
+    while !a:xml.checkctag()
+        let [tagname, attributes]=a:xml.parsetag()
+        if tagname is# 'author'
+            let cs.user=join(a:xml.parsetextintag(), "\n")
+        elseif tagname is# 'date'
+            if s:hasdateexe || executable('date')
+                let s:hasdateexe=1
+                let date=join(a:xml.parsetextintag(), "\n")
+                let dcomponents=matchlist(date,
+                            \             '\v(\d{4,})\-(\d\d)\-(\d\d)'.
+                            \              'T(\d\d)\:(\d\d)\:(\d\d)\.\d+Z')[1:6]
+                if empty(dcomponents)
+                    call s:_f.throw('perrtv', 'yyyy-mm-ddTHH:MM:SS.nnnnnnZ',
+                                \   date)
+                endif
+                let [y, mon, d, h, min, s]=dcomponents
+                let datearg=y.'-'.mon.'-'.d.' '.h.':'.min.':'.s.' UTC'
+                let time=system('date --date='.shellescape(datearg).' +%s')
+                if v:shell_error
+                    call s:_f.throw('derr', datearg, a:csdata[line], time)
+                else
+                    let cs.time=str2nr(time)
+                endif
+            else
+                call s:_f.warn('ndate')
+                let cs.time=0
+            endif
+        elseif tagname is# 'paths'
+            let svnplidx=len(a:repo.svnprefix)-1
+            call a:xml.skipws()
+            while !a:xml.checkctag()
+                let [tagname, attributes]=a:xml.parsetag()
+                if !has_key(attributes, 'action')
+                    call s:_f.throw('perratt', 'action',
+                                \   join(keys(attributes), ", "))
+                elseif tagname isnot# 'path'
+                    call s:_f.throw('perrtag', 'path', tagname)
+                endif
+                if has_key(attributes, 'copyfrom-path')
+                    if attributes['copyfrom-path'][0] isnot# '/'
+                        call s:_f.throw('perratv', '/path/to/file',
+                                    \   'copyfrom-path',
+                                    \   attributes['copyfrom-path'])
+                    endif
+                    let source=attributes['copyfrom-path'][1:]
+                    if svnplidx==-1 || source[:(svnplidx)] is# a:repo.svnprefix
+                        let source=source[(svnplidx+1):]
+                    else
+                        unlet source
+                    endif
+                endif
+                let file=join(a:xml.parsetextintag(), "\n")
+                if file[0] isnot# '/'
+                    call s:_f.throw('perrtv', '/path/to/file', file)
+                endif
+                let file=file[1:]
+                let action=attributes.action
+                if svnplidx==-1 || file[:(svnplidx)] is# a:repo.svnprefix
+                    let file=file[(svnplidx+1):]
+                    if has_key(s:logstatchars, action)
+                        let status=s:logstatchars[action]
+                        let cs.status[status]+=[file]
+                    else
+                        call s:_f.throw('perratv',
+                                    \   join(keys(s:logstatchars, '/')),
+                                    \   'action', action)
+                    endif
+                    if exists('source')
+                        let cs.copies[file]=source
+                    endif
+                endif
+                call a:xml.skipws()
+                unlet! source
+            endwhile
+            for [destination, source] in items(cs.copies)
+                if index(cs.status.removed, source)!=-1
+                    let cs.renames[destination]=source
+                    unlet cs.copies[destination]
+                endif
+            endfor
+            let cs.files=cs.status.added+cs.status.modified
+            let cs.removes=cs.status.removed
+            let cs.changes=cs.files+cs.removes
+            call a:xml.skipctag()
+        elseif tagname is# 'msg'
+            let cs.description=join(a:xml.parsetextintag(), "\n")
         endif
-        let [y, mon, d, h, min, s]=date
-        let datearg=y.'-'.mon.'-'.d.' '.h.':'.min.':'.s.' UTC'
-        let time=system('date --date='.shellescape(datearg).' +%s')
-        if v:shell_error
-            call s:_f.throw('derr', datearg, a:csdata[line], time)
-        else
-            let cs.time=str2nr(time)
-        endif
-    else
-        call s:_f.warn('ndate')
-        let cs.time=0
-    endif
-    let line+=1
-    "▶2 paths
-    if a:csdata[line] is# '<paths>'
-        let line+=1
-        let svnplidx=len(a:repo.svnprefix)-1
-        while line<lcsdata && a:csdata[line] isnot# '</paths>'
-            if a:csdata[line][:4] isnot# '<path' "▶3
-                call s:_f.throw('perr', '<path', a:csdata[line])
-            endif                                "▲3
-            let line+=1
-            let kind=get(matchlist(a:csdata[line], 'kind="\(\w*\)"'), 1, 0)
-            if kind is 0 "▶3
-                call s:_f.throw('perr', 'kind="..."', a:csdata[line])
-            endif          "▲3
-            let line+=1
-            "▶3 Process copies
-            let source=matchstr(a:csdata[line],'\(copyfrom-path="/\)\@<=.*"\@=')
-            if !empty(source)
-                let line+=1
-                let sourcerev=matchstr(a:csdata[line],
-                            \'\v(copyfrom\-rev\=\")@<=\d+\"@=')
-                if empty(sourcerev)
-                    call s:_f.throw('perr', 'copyfrom-rev="N"', a:csdata[line])
-                endif
-                let line+=1
-                if svnplidx==-1 || source[:(svnplidx)] is# a:repo.svnprefix
-                    let source=source[(svnplidx+1):]
-                else
-                    let source=''
-                endif
-            endif
-            "▲3
-            let match=matchlist(a:csdata[line],
-                        \       'action="\(.\)">/\(.\+\)</path>')[1:2]
-            if empty(match) "▶3
-                call s:_f.throw('perr','action="C">/path</path>',a:csdata[line])
-            endif           "▲3
-            let [action, file]=match
-            "▶3 Add file to list(s)
-            if svnplidx==-1 || file[:(svnplidx)] is# a:repo.svnprefix
-                let file=file[(svnplidx+1):]
-                if has_key(s:logstatchars, action)
-                    let status=s:logstatchars[action]
-                    let cs.status[status]+=[file]
-                else
-                    call s:_f.throw('perr', 'action="'.
-                                \           join(keys(s:logstatchars, '/')).'"',
-                                \   action)
-                endif
-                if !empty(source)
-                    let cs.copies[file]=source
-                endif
-            endif
-            "▲3
-            let line+=1
-        endwhile
-        "▶3 Move some copies to renames
-        for [destination, source] in items(cs.copies)
-            if index(cs.status.removed, source)!=-1
-                let cs.renames[destination]=source
-                unlet cs.copies[destination]
-            endif
-        endfor
-        if line>=lcsdata "▶3
-            call s:_f.throw('perr', '</paths>', a:csdata[-1])
-        endif            "▲3
-        let line+=1
-    endif
-    let cs.files=cs.status.added+cs.status.modified
-    let cs.removes=cs.status.removed
-    let cs.changes=cs.files+cs.removes
-    "▶2 description
-    let idx=stridx(a:csdata[line], '<msg>')
-    if idx==-1
-        call s:_f.throw('perr', '<msg>...', a:csdata[line])
-    endif
-    let idx+=5
-    let description=a:csdata[line][(idx):]
-    let line+=1
-    let idx=stridx(description, '</msg>')
-    if idx==-1
-        let cs.description=s:F.decodeentities(description)
-        while line<lcsdata
-            let idx=stridx(a:csdata[line], '</msg>')
-            if idx==-1
-                let cs.description.="\n".s:F.decodeentities(a:csdata[line])
-            else
-                if idx
-                    let cs.description.="\n".a:csdata[line][:(idx-1)]
-                endif
-                break
-            endif
-            let line+=1
-        endwhile
-        let line+=1
-    else
-        let cs.description=s:F.decodeentities(description[:(idx-1)])
-    endif
-    "▶2 check for </logentry>
-    if a:csdata[line] isnot# '</logentry>'
-        call s:_f.throw('perr', '</logentry>', a:csdata[line])
-    endif
-    let line+=1
-    "▲2
-    return [cs, line]
+        call a:xml.skipws()
+    endwhile
+    return cs
 endfunction
 "▶1 getchangesets :: repo → [cs]
 function s:F.getchangesets(repo, ...)
     elseif a:0==2
         let kwargs.revision=a:1.':'.a:2
     endif
-    let log=s:F.svn(a:repo, 'log', args, kwargs, 0, 'logf')[2:-3]
+    let xml=s:_r.xml.new(s:F.svn(a:repo, 'log', args, kwargs, 0, 'logf')[2:])
     let cslist=[]
-    let llog=len(log)
-    let line=0
-    while line<llog
-        let [cs, line]=s:F.parsecs(a:repo, log, line)
+    while !xml.checkctag()
+        let cs=s:F.parsecs(a:repo, xml)
+        call xml.skipctag()
+        call xml.skipws()
         let a:repo.changesets[cs.hex]=cs
         call insert(cslist, cs)
     endwhile
     endif
     "▲2
     let cs=s:F.parsecs(a:repo,
+                \      s:_r.xml.new(
                 \      s:F.svn(a:repo, 'log', ['--', a:repo.svnroot],
                 \              {'revision': rev, 'limit': '1', 'xml': 1,
                 \               'verbose': 1},
-                \              0, 'csf', a:rev), 2)[0]
+                \              0, 'csf', a:rev)[2:]))
     let a:repo.changesets[cs.hex]=cs
     return a:repo.changesets[cs.hex]
 endfunction
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.