Commits

ZyX_I committed 55425fe

Added more argument checks
Fixed not working resolve
Fixed code that adds slashes to plid (it now may add more then one slash)
Added `provides' dictionary to plugdict
Fixed loadplugin function
Added support for recursive dependencies

  • Participants
  • Parent commits 38469fe

Comments (0)

Files changed (1)

File plugin/frawor.vim

 let s:F={}
 "▶2 s:g
 let s:g={}
+let s:g._pluginloaded=1
 let s:g.pls={} " Plugin dictionaries
-let s:g.pltypes={}
-let s:g.features={
-            \     'index': {},
-            \    'onload': {},
-            \  'onunload': {},
-        \}
+let s:g.loading={}
+let s:g.features={}
+let s:g.dependents={}
 "▶2 Messages
 if v:lang=~?'ru'
     let s:g._messages={
                 \                'в каталоге %s',
                 \   'doublereg': 'Попытка повторной регистрации %s '.
                 \                'заблокирована',
+                \  'filenotstr': 'Третий аргумент функции FraworRegister '.
+                \                'должен быть строкой',
+                \   'fileempty': 'Третий аргумент функции FraworRegister пуст',
                 \  'vernotlist': 'Версия дополнения %s должна быть списком',
                 \    'vershort': 'Версия дополнения %s должна содержать '.
                 \                'не менее двух компонент',
                 \                'но была загружена %s',
                 \   'reqfailed': 'Не удалось загрузить зависимость %s '.
                 \                'дополнения %s',
+                \      'recdep': 'Дополнения %s и %s рецурсивно зависят '.
+                \                'друг от друга',
                 \    'gnotdict': 'Ошибка регистрации %s: '.
                 \                'предпоследний аргумент FraworRegister '.
                 \                'не является словарём',
                 \    'Fnotdict': 'Ошибка регистрации %s: '.
                 \                'последний аргумент FraworRegister '.
-                \                'не является словарём (%s)',
+                \                'не является словарём',
                 \   'sidnotnum': 'Ошибка регистрации %s: второй аргумент '.
                 \                'FraworRegister не является числом',
-                \   'sidnonneg': 'Ошибка регистрации %s: второй аргумент '.
+                \   'sidnonpos': 'Ошибка регистрации %s: второй аргумент '.
                 \                'FraworRegister должен быть '.
                 \                'либо положителен, либо '.
                 \                '(если первый аргумент равен нулю) нулём',
                 \'plregistered': 'Plugin %s was already registered '.
                 \                'in directory %s',
                 \   'doublereg': 'Refusing to register %s for the second time',
+                \  'filenotstr': 'Third argument to FraworRegister '.
+                \                'must be a String',
+                \   'fileempty': 'Third argument to FraworRegister is empty',
                 \  'vernotlist': 'Version argument of plugin %s is not a List',
                 \    'vershort': 'Version argument of plugin %s must have '.
                 \                'at least two components',
                 \  'oldversion': 'Detected too old dependency %s '.
                 \                'of a plugin %s: expected %s, but got %s',
                 \   'reqfailed': 'Failed to load dependency %s of a plugin %s',
+                \      'recdep': 'Plugins %s and %s are recursively dependent',
                 \    'gnotdict': 'Error while registering %s: '.
                 \                'last but one argument to FraworRegister '.
                 \                'must be a Dictionary',
                 \                'must be a Dictionary',
                 \   'sidnotnum': 'Error while registering %s: second argument '.
                 \                'to FraworRegister must be a number',
-                \   'sidnonneg': 'Error while registering %s: second argument '.
+                \   'sidnonpos': 'Error while registering %s: second argument '.
                 \                'to FraworRegister must either be positive '.
                 \                'or equal to 0 (if first argument '.
                 \                'is equal to 0 too)',
 endfunction
 "▶1 parseplugpath
 function s:F.parseplugpath(file)
-    let rtps=map(split(&runtimepath, '\v%(\\@<!\\%(\\\\)*)@<!,'),
-                \'resolve(fnamemodify(substitute(substitute(v:val, '.
-                \                                          '''\\\([\\,]\)'', '.
-                \                                          '"\\1", "g"), '.
-                \                                '''\$\w\+'', '.
-                \                                '"\\=eval(submatch(0))","g"),'.
-                \                    '":p"))')
+    "   (slit(...))                 split runtimepath on non-escaped «,»
+    " → (last substitute)           unescape commas and backward slashes only
+    " → (last but one substitute)   expand ${VAR} using eval()
+    " → (fnamemodify(..., ":p"))    turn path into full
+    " → (filter(..., isdirectory))  filter out existing directories
+    " → (v:val[:-2])                remove trailing path separator (resolve does
+    "                               not work with it)
+    " → (resolve(...))              resolve symbolic links
+    let rtps=map(filter(map(split(&runtimepath, '\v%(\\@<!\\%(\\\\)*)@<!,'),
+                \'fnamemodify(substitute(substitute(v:val, '.
+                \                                  '''\\\([\\,]\)'', '.
+                \                                  '"\\1", "g"), '.
+                \                        '''\$\w\+'', '.
+                \                        '"\\=eval(submatch(0))","g"),'.
+                \            '":p")'), 'isdirectory(v:val)'),
+                \'resolve(v:val[:-2])')
     let file=fnamemodify(a:file, ':p')
+    " XXX fnamemodify here removes trailing path separator as well. This 
+    " behavior must be kept in order to work with resolve()
     let curpath=fnamemodify(file, ':h')
     let removedcomponents=[fnamemodify(file, ':t:r')]
     let foundrtp=""
         return [get(removedcomponents, 0, 'plugin'),
                     \join(removedcomponents, '/'), foundrtp]
     else
-        return ['/unknown', join(removedcomponents,'/'), '']
+        return ['/unknown', join(removedcomponents, '/'), '']
     endif
 endfunction
 "▶1 newplugin
 function s:F.newplugin(version, sid, file, dependencies, oneload, g, F)
+    "▶2 Checking whether a:file is a string
+    if type(a:file)!=type("")
+        call s:F._frawor.throw('filenotstr')
+    elseif empty(a:file)
+        call s:F._frawor.throw('fileempty')
+    endif
     "▶2 plugtype, plid, plrtp, plversion
     if a:version is 0
         let plugtype='/anonymous'
         let plid=a:file
         let plrtp=''
         let plversion=[0, 0]
-    "▶3 Verifying plugin version
-    elseif type(a:version)!=type([])
-        call s:F._frawor.throw('vernotlist', plid)
-    elseif len(a:version)<2
-        call s:F._frawor.throw('vershort', plid)
-    elseif !empty(filter(copy(a:version),'type(v:val)!='.type(0).' || v:val<0'))
-        call s:F._frawor.throw('vernotnum', plid)
-    "▲3
     else
         let [plugtype, plid, plrtp]=s:F.parseplugpath(a:file)
+        "▶3 Verifying plugin version
+        if type(a:version)!=type([])
+            call s:F._frawor.throw('vernotlist', plid)
+        elseif len(a:version)<2
+            call s:F._frawor.throw('vershort', plid)
+        elseif !empty(filter(copy(a:version),
+                    \        'type(v:val)!='.type(0).' || v:val<0'))
+            call s:F._frawor.throw('vernotnum', plid)
+        endif
+        "▲3
         let plversion=a:version
     endif
     "▶2 Checking whether a:g and a:F are dictionaries and a:sid is number
     elseif type(a:sid)!=type(0)
         call s:F._frawor.throw('sidnotnum', plid)
     elseif !(a:sid>0 || (type(a:version)==type(0) && a:sid==0))
-        call s:F._frawor.throw('sidnonneg', plid)
+        call s:F._frawor.throw('sidnonpos', plid)
     endif
     "▶2 Checking for double registration
     if has_key(s:g.pls, plid)
         if plugtype[0]!=#'/'
             call s:F._frawor.warn('plregistered', plid, plrtp)
         endif
-        let plid.='/'
+        while has_key(s:g.pls, plid)
+            let plid.='/'
+        endwhile
     endif
     "▶2 Constructing plugdict
     "▶3 Some trivial construction
                 \   'intprefix': 's:g.pls['.
                 \                   substitute(string(plid), '\n', '''."\n".''',
                 \                              'g').']',
+                \    'provides': {},
             \}
     let plugdict.F._frawor={}
     "▶3 Processing dependencies
         endif
         "▲4
         let plugdict.dependencies[dplid]=d.Version
+        if !has_key(s:g.dependents, dplid)
+            let s:g.dependents[dplid]={}
+        endif
+        let s:g.dependents[dplid][plid]=1
     endfor
     lockvar! plugdict.dependencies
-    "▶2 Adding plugdict to global variables
-    if !has_key(s:g.pltypes, plugtype)
-        let s:g.pltypes[plugtype]={plid : plugdict}
-    else
-        let s:g.pltypes[plugtype][plid]=plugdict
-    endif
-    let s:g.pls[plid]=plugdict
     "▶2 Locking plugdict
     lockvar 1 plugdict
     lockvar plugdict.version
                 \         'id': plugdict.id,
                 \'runtimepath': plugdict.runtimepath,
             \}
+    let s:g.pls[plid]=plugdict
     if a:oneload
         call s:F.loadplugin(plid)
     endif
     return r
 endfunction
+"▶1 addfeature
+function s:F.addfeature(plugdict, feature)
+    "▶2 Feature provides constructed function
+    if has_key(a:feature, 'cons')
+        execute  'function a:plugdict.F._frawor.'.a:feature.id."(...)\n".
+                    \'    return call(s:g.features.'.a:feature.id.'.cons, '.
+                    \                '['.a:plugdict.intprefix."]+a:000, {})\n".
+                    \'endfunction'
+    endif
+    "▶2 Feature registration
+    if has_key(a:feature, 'load')
+        let d.Result=a:feature.load(a:plugdict)
+        let a:plugdict.features[a:feature.id]=((d.Result is 0)?({}):(d.Result))
+    elseif has_key(a:feature, 'init')
+        let a:plugdict.features[a:feature.id]=deepcopy(a:feature.init)
+    endif
+    "▲2
+    return a:feature
+endfunction
 "▶1 loadplugin
 function s:F.loadplugin(plid)
+    if has_key(s:g.loading, a:plid)
+        return 2
+    endif
     if !has_key(s:g.pls, a:plid)
-        execute 'runtime! '.fnameescape(a:plid)
+        execute 'runtime! '.fnameescape(a:plid.'.vim')
     endif
     if !has_key(s:g.pls, a:plid)
         return 0
     endif
     let plugdict=s:g.pls[a:plid]
     if plugdict.status!=2
+        let s:g.loading[a:plid]=1
         let d={}
-        "▶2 Loading dependencies
-        for [dplid, d.Version] in items(plugdict.dependencies)
-            if type(d.Version)!=type([])
-                continue
+        try
+            "▶2 Loading dependencies
+            for [dplid, d.Version] in items(plugdict.dependencies)
+                if type(d.Version)!=type([])
+                    continue
+                endif
+                if has_key(s:g.loading, dplid)
+                    call s:F._frawor.warn('recdep', dplid, a:plid)
+                    continue
+                endif
+                if s:F.loadplugin(dplid)
+                    let dversion=s:g.pls[dplid].version
+                    "▶3 Checking dependency version
+                    if d.Version[0]!=dversion[0]
+                        call s:F._frawor.throw('majmismatch', dplid, a:plid,
+                                    \                         d.Version[0],
+                                    \                         dversion[0])
+                    elseif s:F.compareversions(d.Version, dversion)>0
+                        call s:F._frawor.throw('oldversion', dplid, a:plid,
+                                    \                      join(d.Version, '.'),
+                                    \                      join(dversion,  '.'))
+                    endif
+                    "▲3
+                else
+                    call s:F._frawor.throw('reqfailed', dplid, a:plid)
+                endif
+                call map(values(s:g.pls[dplid].provides),
+                            \'s:F.addfeature(plugdict, v:val)')
+            endfor
+            lockvar 1 plugdict.features
+            "▲2
+            if !plugdict.oneload
+                execute 'source '.fnameescape(plugdict.file)
             endif
-            if s:F.loadplugin(dplid)
-                let dversion=s:g.pls[dplid].version
-                "▶3 Checking dependency version
-                if d.Version[0]!=dversion[0]
-                    call s:F._frawor.throw('majmismatch', dplid, a:plid,
-                                \                         d.Version[0],
-                                \                         dversion[0])
-                elseif s:F.compareversions(d.Version, dversion)<0
-                    call s:F._frawor.throw('oldversion', dplid, a:plid,
-                                \                        join(d.Version[0],'.'),
-                                \                        join(dversion,    '.'))
-                endif
-                "▲3
-            else
-                call s:F._frawor.throw('reqfailed', dplid, a:plid)
+            let plugdict.status=2
+            lockvar! plugdict.status
+            "▶2 Adding features to already loaded plugins
+            if has_key(s:g.dependents, a:plid)
+                for rplugdict in map(keys(s:g.dependents[a:plid]),
+                            \        's:g.pls[v:val]')
+                    call map(values(plugdict.provides),
+                                \'s:F.addfeature(rplugdict, v:val)')
+                endfor
             endif
-        endfor
-        "▶2 Calling features
-        for feature in values(s:g.features.index)
-            "▶3 Forbid features that are not plugin dependencies
-            if !has_key(plugdict.dependencies, feature.plid)
-                continue
-            endif
-            "▶3 Feature provides constructed function
-            if has_key(feature, 'cons')
-                execute  'function plugdict.F._frawor.'.feature.id."(...)\n".
-                        \'    return call(s:g.features.index.'.
-                        \                               feature.id.'.cons, '.
-                        \                '['.plugdict.intprefix."]+a:000,{})\n".
-                        \'endfunction'
-            endif
-            "▶3 Feature registration
-            if has_key(feature, 'load')
-                let d.Result=feature.load(plugdict)
-                let plugdict.features[feature.id]=
-                            \((d.Result is 0)?({}):(d.Result))
-            elseif has_key(feature, 'init')
-                let plugdict.features[feature.id]=deepcopy(feature.init)
-            else
-                continue
-            endif
-            "▲3
-        endfor
-        lockvar 1 plugdict.features
-        "▲2
-        if !plugdict.oneload
-            execute 'source '.fnameescape(plugdict.file)
-        endif
-        let plugdict.status=2
-        lockvar! plugdict.status
+            "▲2
+        finally
+            unlet s:g.loading[a:plid]
+        endtry
     endif
     return 1
 endfunction
 function FraworRegister(...)
     return call(s:F.newplugin, a:000, {})
 endfunction
-"▶1 cons.warn
-let s:g.features.index.warn={
+"▶1 features.warn
+let s:g.features.warn={
             \'plid': 'plugin/frawor',
             \  'id': 'warn',
         \}
-function s:g.features.index.warn.cons(plugdict, msgid, ...)
+function s:g.features.warn.cons(plugdict, msgid, ...)
     if !has_key(a:plugdict.g, '_messages')
         call s:F._frawor.throw('nomessages', a:plugdict.id)
     elseif !has_key(a:plugdict.g._messages, a:msgid)
     echohl None
     return 0
 endfunction
-"▶1 cons.throw
-let s:g.features.index.throw={
+"▶1 features.throw
+let s:g.features.throw={
             \'plid': 'plugin/frawor',
             \  'id': 'throw',
         \}
-function s:g.features.index.throw.cons(plugdict, msgid, ...)
-    call call(s:g.features.cons.warn, [a:plugdict, a:msgid]+a:000, {})
+function s:g.features.throw.cons(plugdict, msgid, ...)
+    call call(s:g.features.warn.cons, [a:plugdict, a:msgid]+a:000, {})
     let throwmsg='Frawor:'.a:plugdict.id.':'.a:msgid
     throw throwmsg
 endfunction
 "▶1 Plugin registration
 call s:F.newplugin([0, 0], s:Eval('+matchstr(expand("<sfile>"), ''\d\+'')'),
             \      expand('<sfile>:p'), {}, 1, s:g, s:F)
+"▶1 XXX adding missing features
+let s:plugdict=s:g.pls['plugin/frawor']
+let s:plugdict.provides.warn=s:g.features.warn
+let s:plugdict.provides.throw=s:g.features.throw
+call s:F.addfeature(s:plugdict, s:g.features.warn)
+call s:F.addfeature(s:plugdict, s:g.features.throw)
+unlet s:plugdict
 
 " vim: fmr=▶,▲ sw=4 ts=4 sts=4 et tw=80