Commits

ZyX_I committed bc93a62

@aurum/repo: Instead of depending on drivers, make them depend on @aurum/repo

  • Participants
  • Parent commits 8220d8d

Comments (0)

Files changed (15)

File README.markdown

 This plugin provides a vim <--> VCS (currently only mercurial) integration for 
 your projects. Features:
 
-  - Partially committing changes ([:AuRecord](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line327-0)).
+  - Partially committing changes ([:AuRecord](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line329-0)).
 
-  - Viewing file state at particular revision ([aurum://file](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line558-0), [:AuFile](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line148-0)).
+  - Viewing file state at particular revision ([aurum://file](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line560-0), [:AuFile](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line150-0)).
 
   - Viewing uncommited changes in a vimdiff, as well as changes between 
-    specific revisions ([:AuVimDiff](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line367-0)). It is also possible to open multiple 
+    specific revisions ([:AuVimDiff](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line369-0)). It is also possible to open multiple 
     tabs with all changes to all files viewed as side-by-side diffs.
 
-  - Viewing revisions log ([:AuLog](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line228-0)). Output is highly customizable.
+  - Viewing revisions log ([:AuLog](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line230-0)). Output is highly customizable.
 
-  - Viewing working directory status ([:AuStatus](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line331-0)).
+  - Viewing working directory status ([:AuStatus](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line333-0)).
 
-  - Commiting changes ([:AuCommit](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line89-0)), commit messages are remembered in case of 
-    rollback ([g:aurum_remembermsg](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line803-0)).
+  - Commiting changes ([:AuCommit](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line91-0)), commit messages are remembered in case of 
+    rollback ([g:aurum_remembermsg](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line805-0)).
 
   - Obtaining various URL’s out of remote repository URL (like URL of the HTML 
     version of the current file with URL fragment pointing to the current line 
-    attached: useful for sharing) ([:AuHyperlink](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line179-0)).
+    attached: useful for sharing) ([:AuHyperlink](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line181-0)).
 
   - aurum#changeset(), aurum#repository() and aurum#status() functions 
     that are to be used from modeline.
 
   - Frontends for various other VCS commands.
 
-Most commands can be reached with a set of mappings (see [aurum-mappings](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line700-0)), 
+Most commands can be reached with a set of mappings (see [aurum-mappings](http://vimpluginloader.sourceforge.net/doc/aurum.txt.html#line702-0)), 
 all mappings are customizable.
 
 

File doc/aurum.txt

     8. Internal objects                                  |aurum-objects|
         8.1. Repository                                  |aurum-repository|
         8.2. Changeset                                   |aurum-changeset|
+    9. Creating your own driver                          |aurum-new-driver|
+    10. Changelog                                        |aurum-changelog|
 
 ==============================================================================
 1. Intro                                                         *aurum-intro*
     you want to add support for particular remote repository.
 functions :: {String : FRef}                            *aurum-repo.functions*
     Dictionary that contains all driver-specific functions. All functions 
-    except |aurum-rf-repo| accept repository object as its first argument, so 
-    it is not mentioned in function descriptions. Here is the list:
-  repo :: path -> repo                                         *aurum-rf-repo*
-    Creates new repository object using repository located at given path.
-    Note you must not ever use this directly, this is to be called by repo.get 
-         function from plugin/aurum/repo. If you use it directly, you will be 
-         missing some setup that will render newly created object useless.
+    except |aurum-rf-repo|, |aurum-rf-checkdir| and |aurum-rf-checkremote| 
+    accept repository object as its first argument, so it is not mentioned in 
+    function descriptions. Here is the list:
                                                              *aurum-rf-commit*
   commit :: message[, [ file ][, user[, date[, closebranch[, force]]]]] -> _
     Commit changes made to specified files (default depends on VCS, normally 
     Updates data stored in repository. Is called automatically when you get 
     the repository object unless this object is obtained from cash used by 
     |aurum#repository|.
+  reltorepo :: repo, path -> path                         *aurum-rf-reltorepo*
+    Turn absolute or relative to current directory path into relative to 
+    repository root one.
+The following functions do not accept repo argument:
+  repo :: path -> repo                                         *aurum-rf-repo*
+    Creates new repository object using repository located at given path.
+    Note you must not ever use this directly, this is to be called by repo.get 
+         function from plugin/aurum/repo. If you use it directly, you will be 
+         missing some setup that will render newly created object useless.
+  checkdir    :: path -> Bool                           *aurum-rf-checkdir*
+  checkremote :: path -> Bool                           *aurum-rf-checkremote*
+    These two functions check whether given path contains repository supported 
+    by specific driver. First is called once for each directory in path, 
+    ending with root (drive name like “C:” in windows, “/” in POSIX OS’es) and 
+    is likely to contain something as simple as >
+        function s:driver.checkdir(path)
+            return isdirectory(a:path."/.hg")
+        endfunction
+<   . Second is called once on paths that contain “://” in their string (it is 
+    likely to return just 0 because normal work with remote repositories is 
+    impossible in most VCS’es).
 
 ------------------------------------------------------------------------------
 8.2. Changeset                                               *aurum-changeset*
 renames      Dictionary {newname : oldname}                 *aurum-cs.renames*
 copies       Dictionary {copyname : originname}              *aurum-cs.copies*
 
+==============================================================================
+9. Creating your own driver                                 *aurum-new-driver*
+                                                           *aurum-f-regdriver*
+In order to create driver you must use “regdriver” feature defined in 
+plugin/aurum/repo (you must specify it in dependencies and then you will get 
+|s:_f|.regdriver() function). This feature accepts two arguments: name of the 
+driver (only latin letters, digits and underscores are allowed, see |/\w|) and 
+dictionary with repository functions (only function references are allowed):
+Required functions: |aurum-rf-repo|, |aurum-rf-getcs|, |aurum-rf-checkdir|.
+Optional functions:
+  Function               Default behavior ~
+  |aurum-rf-dirty|         Use |aurum-rf-status| and check whether file has 
+                         status different from “ignored” or “clean”
+  |aurum-rf-getnthparent|  Use |aurum-rf-getcs| and |aurum-rf-getcsprop| 
+                         (properties "parents" or "children")
+  |aurum-rf-getcsprop|     Checks whether requested key is present in the 
+                         changeset object. If yes returns its value, otherwise 
+                         throws an exception.
+  |aurum-rf-reltorepo|     Uses |aurum-repo.path| to get requested path.
+  |aurum-rf-difftobuffer|  Wrapper around |aurum-rf-diff|.
+  |aurum-rf-move|          Uses |aurum-rf-copy| and |aurum-rf-remove| to 
+                         emulate renaming command.
+  |aurum-rf-remove|        Uses |aurum-rf-forget| to untrack file and then 
+                         |delete()|s it.
+  |aurum-rf-checkremote|   Returns 0.
+Other functions, mentioned in |aurum-repo.functions|, but not mentioned here, 
+throw an "nimp" exception.
+
+==============================================================================
+10. Changelog                                                *aurum-changelog*
+
+@aurum/repo:
+    1.0: Instead of depending on drivers, make drivers depend on @aurum/repo
+
 vim: ft=help:tw=78

File ftplugin/aurumannotate.vim

 endif
 setlocal noswapfile
 setlocal nomodeline
-execute frawor#Setup('0.0', {'@aurum/repo': '0.0',
+execute frawor#Setup('0.0', {'@aurum/repo': '1.0',
             \             '@aurum/bufvars': '0.0',
             \             '@aurum/vimdiff': '0.0',
             \            '@aurum/annotate': '0.0',

File ftplugin/aurumlog.vim

 setlocal nomodeline
 execute frawor#Setup('0.0', {'@aurum/cmdutils': '0.0',
             \                 '@aurum/bufvars': '0.0',
-            \                    '@aurum/repo': '0.0',
+            \                    '@aurum/repo': '1.0',
             \                    '@aurum/edit': '0.0',
             \                           '@/os': '0.0',
             \                     '@/mappings': '0.0',})

File plugin/aurum.vim

                 \             '@aurum/status': '0.0',
                 \                '@aurum/log': '0.0',
                 \             '@aurum/commit': '0.0',
-                \               '@aurum/repo': '0.0',
+                \               '@aurum/repo': '1.0',
                 \               '@aurum/edit': '0.0',
                 \            '@aurum/bufvars': '0.0',
                 \            '@aurum/vimdiff': '0.0',}, 0)

File plugin/aurum/cmdutils.vim

 if !exists('s:_pluginloaded')
     execute frawor#Setup('0.0', {'@/resources': '0.0',
                 \                       '@/os': '0.0',
-                \                '@aurum/repo': '0.0',
+                \                '@aurum/repo': '1.0',
                 \                '@aurum/edit': '0.0',
                 \               '@aurum/cache': '0.0',
                 \             '@aurum/bufvars': '0.0',}, 0)

File plugin/aurum/commit.vim

                 \              '@aurum/status': '0.0',
                 \            '@aurum/cmdutils': '0.0',
                 \             '@aurum/bufvars': '0.0',
-                \                '@aurum/repo': '0.0',
+                \                '@aurum/repo': '1.0',
                 \                '@aurum/edit': '0.0',
                 \                      '@/fwc': '0.3',
                 \                 '@/commands': '0.0',

File plugin/aurum/diff.vim

 if !exists('s:_pluginloaded')
     execute frawor#Setup('0.0', {'@aurum/cmdutils': '0.0',
                 \                 '@aurum/bufvars': '0.0',
-                \                    '@aurum/repo': '0.0',
+                \                    '@aurum/repo': '1.0',
                 \                    '@aurum/edit': '0.0',
                 \                           '@/os': '0.0',
                 \                          '@/fwc': '0.0',

File plugin/aurum/drivers/mercurial.vim

 scriptencoding utf-8
 if !exists('s:_pluginloaded')
     execute frawor#Setup('0.0', {'@/python': '0.0',
-                \             '@/resources': '0.0',
+                \             '@aurum/repo': '1.0',
                 \                    '@/os': '0.0',}, 0)
     finish
 elseif s:_pluginloaded
 "  http://hg.savannah.nongnu.org/hgweb/mechsys/
 "  https://sharesource.org/hg/alqua/
 "  http://mercurial.tuxfamily.org/mercurialroot/slitaz/tazlito/
+"  git://repo.or.cz/vcscommand
 let s:ghpath='substitute(path, "\\v^[:/]|\\.git$", "", "g")'
 let s:gcproj='matchstr(domain, "\\v^[^.]+")'
 let s:pkbase='"http://".matchstr(domain, ''\v[^.]+\.[^.]+$'')."/projects/".matchstr(path, ''\v.*\/\zs[^~]+'').'.
             \                                                '"/sources/". matchstr(path, "\\v[^~]+$")'
 let s:cpbase='"http://".path[1:].".codeplex.com/SourceControl'
 let s:cbbase='"https://".%s.".".domain."/projects/".%s."/repositories/".%s'
+let s:robase='"http://".domain."/w".substitute(path, ''\v(\.git)?\/*$'', ".git", "")'
 let s:cbssh=printf(s:cbbase, 'matchstr(path, "\\v^[^/]+", 1)',
             \                'matchstr(path, ''\v[^/]+%(\/[^/]+\/?$)'')',
             \                'matchstr(path[:-4], "\\v[^/]+$")')
 \        'log': s:pkbase.'."/history"',
 \      'clone': '"https://".domain."/hg/".matchstr(path, "\\v[^/]+$")',
 \       'push': '"ssh://".domain."/".matchstr(path, "\\v[^/]+$")',}],
+\['domain is? "repo.or.cz"',
+\ {     'html': s:robase.'."/blob/".'.s:gb.'.":/".file',  'hline': '"l".line',
+\        'raw': s:robase.'."/blob_plain/".'.s:gb.'.":/".file',
+\   'annotate': s:robase.'."/blame/".'.s:gb.'.":/".file', 'aline': '"l".line',
+\   'filehist': s:robase.'."/history/".'.s:gb.'.":/".file',
+\  'changeset': s:robase.'."/commit/".'.s:gb,
+\        'log': s:robase.'."/log/".'.s:gb,
+\      'clone': '"http://".domain.path',}],
 \['domain is? "sharesource.org" && path[:2] is? "/hg"',
 \ map(copy(s:hgwebdict), 'substitute(v:val, "http", "https", "")')],
 \[ 'domain =~? ''\v^%(mercurial\.%(intuxication|tuxfamily)|hg\.mozdev|hg\.savannah\.%(non)?gnu)\.org$'' || '.
 \ '('.s:dl.'=~#''\V<link rel="icon" href="\[^"]\*static/hgicon.png" type="image/png" />'')',
 \ s:hgwebdict],
 \]
-unlet s:ghpath s:gcproj s:cbssh s:cbhttps s:pkbase s:cpbase s:gb s:dl s:hgwebdict s:bbdict
+unlet s:ghpath s:gcproj s:cbssh s:cbhttps s:pkbase s:cpbase s:gb s:dl s:hgwebdict s:bbdict s:robase
 "▶1 removechangesets :: repo, start_rev_num → + repo
 function s:F.removechangesets(repo, start)
     let changesets=a:repo.changesets
     return r
 endfunction
 endif
-"▶1 Post resource
-call s:_f.postresource('mercurial', s:hg)
+"▶1 hg.checkdir :: dir → Bool
+function s:hg.checkdir(dir)
+    return s:_r.os.path.isdir(s:_r.os.path.join(a:dir, '.hg'))
+endfunction
+"▶1 Register driver
+call s:_f.regdriver('Mercurial', s:hg)
 "▶1
 call frawor#Lockvar(s:, '_pluginloaded')
 " vim: ft=vim ts=4 sts=4 et fmr=▶,▲

File plugin/aurum/edit.vim

     execute frawor#Setup('0.0', {'@/autocommands': '0.0',
                 \                   '@/functions': '0.0',
                 \                   '@/resources': '0.0',
-                \                   '@aurum/repo': '0.0',
+                \                   '@aurum/repo': '1.0',
                 \                '@aurum/bufvars': '0.0',}, 0)
     call FraworLoad('@/autocommands')
     call FraworLoad('@/functions')

File plugin/aurum/file.vim

     execute frawor#Setup('0.0', {'@aurum/cmdutils': '0.0',
                 \                 '@aurum/bufvars': '0.0',
                 \                 '@aurum/vimdiff': '0.0',
-                \                    '@aurum/repo': '0.0',
+                \                    '@aurum/repo': '1.0',
                 \                    '@aurum/edit': '0.0',
                 \                           '@/os': '0.0',
                 \                          '@/fwc': '0.0',

File plugin/aurum/log.vim

                 \         '@aurum/bufvars': '0.0',
                 \            '@aurum/edit': '0.0',
                 \                  '@/fwc': '0.3',
-                \            '@aurum/repo': '0.0',
+                \            '@aurum/repo': '1.0',
                 \             '@/commands': '0.0',
                 \            '@/functions': '0.0',
                 \              '@/options': '0.0',}, 0)

File plugin/aurum/record.vim

                 \                   '@/functions': '0.0',
                 \                 '@aurum/commit': '0.0',
                 \               '@aurum/cmdutils': '0.0',
-                \                   '@aurum/repo': '0.0',
+                \                   '@aurum/repo': '1.0',
                 \                   '@aurum/edit': '0.0',
                 \                     '@/options': '0.0',}, 0)
     call FraworLoad('@/commands')

File plugin/aurum/repo.vim

 "▶1
 scriptencoding utf-8
 if !exists('s:_pluginloaded')
-    execute frawor#Setup('0.0', {'@/resources': '0.0',
+    execute frawor#Setup('1.0', {'@/resources': '0.0',
                 \                       '@/os': '0.0',
                 \                  '@/options': '0.0',
-                \   '@aurum/drivers/mercurial': '0.0',
                 \             '@aurum/bufvars': '0.0',}, 0)
     finish
 elseif s:_pluginloaded
     finish
 endif
-let s:drivers={'mercurial': s:_r.mercurial}
+let s:drivers={}
+let s:repos={}
 let s:_options={
             \'diffopts':  {'default': {},
             \              'checker': 'dict {numlines         range 0 inf '.
             \          'v:val is# "numlines" ? '.
             \               '" ?".v:val." range 0 inf" : '.
             \               '"!?".v:val'))
-let s:repos={}
+let s:_messages={
+            \  'nrm': 'Failed to remove file %s from repository %s',
+            \'iname': 'Error while registering driver for plugin %s: '.
+            \         'invalid name: it must be a non-empty sting, '.
+            \         'containing only latin letters, digits and underscores',
+            \ 'nimp': 'Function %s was not implemented in driver %s',
+            \'uprop': 'Unable to obtain property %s from changeset %s '.
+            \         'in repository %s',
+        \}
+call extend(s:_messages, map({
+            \ 'dreg': 'driver was already registered by plugin %s',
+            \'fndct': 'second argument is not a dictionary',
+            \ 'fmis': 'some required functions are missing',
+            \ 'nfun': 'some of dictionary values are not '.
+            \         'callable function references',
+        \}, '"Error while registering driver %s for plugin %s: ".v:val'))
+let s:deffuncs={}
 "▶1 dirty :: repo, file → Bool
-function s:F.dirty(repo, file)
+function s:deffuncs.dirty(repo, file)
     let status=a:repo.functions.status(a:repo, 0, 0, [a:file])
     for [type, files] in items(status)
         if type is# 'ignored' || type is# 'clean'
     return 0
 endfunction
 "▶1 getnthparent :: repo, rev, n → cs
-function s:F.getnthparent(repo, rev, n)
+function s:deffuncs.getnthparent(repo, rev, n)
     let r=a:repo.functions.getcs(a:repo, a:rev)
     let key=((a:n>0)?('parents'):('children'))
     for i in range(1, abs(a:n))
     endfor
     return r
 endfunction
+"▶1 getcsprop :: repo, cs|rev, prop → prop
+function s:deffuncs.getcsprop(repo, cs, prop)
+    if type(a:cs)!=type({})
+        let cs=a:repo.functions.getcs(a:repo, a:cs)
+    else
+        let cs=a:cs
+    endif
+    if has_key(cs, a:prop)
+        return cs[a:prop]
+    else
+        call s:_f.throw('uprop', a:prop, a:cs.hex, a:repo.path)
+    endif
+endfunction
 "▶1 reltorepo :: repo, path → rpath
-function s:F.reltorepo(repo, path)
+function s:deffuncs.reltorepo(repo, path)
     return join(s:_r.os.path.split(s:_r.os.path.relpath(a:path,
                 \                                       a:repo.path))[1:], '/')
 endfunction
 "▶1 difftobuffer
-function s:F.difftobuffer(repo, buf, ...)
+function s:deffuncs.difftobuffer(repo, buf, ...)
     let diff=call(a:repo.functions.diff, [a:repo]+a:000, {})
     let oldbuf=bufnr('%')
     if oldbuf!=a:buf
         execute 'buffer' oldbuf
     endif
 endfunction
-"▶1 repotype :: path → Maybe String
-function s:F.repotype(path)
-    if a:path=~#'\v^\w+%(\+\w+)*\V://' ||
-                \s:_r.os.path.isdir(s:_r.os.path.join(a:path, '.hg'))
-        return 'mercurial'
+"▶1 move
+function s:deffuncs.move(repo, force, source, target)
+    call a:repo.functions.copy(a:repo, a:force, a:source, a:target)
+    call a:repo.functions.remove(a:repo, a:source)
+endfunction
+"▶1 remove
+function s:deffuncs.remove(repo, file)
+    call a:repo.functions.forget(a:repo, a:file)
+    let file=s:_r.os.path.join(a:repo.path, a:file)
+    if s:_r.os.path.isfile(file)
+        if delete(file)
+            call s:_f.throw('nrm', a:file, a:repo.path)
+        endif
     endif
+endfunction
+"▶1 checkremote
+function s:deffuncs.checkremote(...)
+    return 0
+endfunction
+"▶1 getrevhex
+function s:deffuncs.getrevhex(repo, rev)
+    return a:rev.''
+endfunction
+"▶1 getdriver :: path, type → Maybe driver
+function s:F.getdriver(path, ptype)
+    for driver in values(s:drivers)
+        if driver.functions['check'.a:ptype](a:path)
+            return driver
+        endif
+    endfor
     return 0
 endfunction
 "▶1 getrepo :: path → repo
 function s:F.getrepo(path)
+    "▶2 Pull in drivers if there are no
+    if empty(s:drivers)
+        for src in s:_r.os.listdir(s:_r.os.path.join(s:_frawor.runtimepath,
+                    \              'plugin', 'aurum', 'drivers'))
+            if len(src)<5 || src[-4:] isnot# '.vim'
+                continue
+            endif
+            call FraworLoad('@aurum/drivers/'.src[:-5])
+        endfor
+    endif
+    "▶2 Get path
     if empty(a:path)
         let path=s:_r.os.path.realpath('.')
     elseif a:path is# ':'
     else
         let path=a:path
     endif
+    "▶2 Get driver
     if stridx(path, '://')==-1
         let olddir=''
-        while path isnot# olddir && s:F.repotype(path) is 0
+        let driver=0
+        while path isnot# olddir
+            unlet driver
+            let driver=s:F.getdriver(path, 'dir')
+            if driver isnot 0
+                break
+            endif
             let olddir=path
             let path=fnamemodify(path, ':h')
         endwhile
+    else
+        let driver=s:F.getdriver(path, 'remote')
     endif
+    if driver is 0
+        return 0
+    endif
+    "▲2
     if has_key(s:repos, path)
         let repo=s:repos[path]
         if !empty(repo.cslist)
         endif
         return repo
     endif
-    let repotype=s:F.repotype(path)
-    if repotype is 0
-        return 0
-    endif
-    let repo=s:drivers[repotype].repo(path)
+    let repo=driver.functions.repo(path)
     if repo is 0
         return 0
     endif
-    let repo.type=repotype
+    let repo.type=driver.id
     let repo.path=path
     if !has_key(repo, 'functions')
-        let repo.functions=copy(s:drivers[repotype])
-    endif
-    if !has_key(repo.functions, 'difftobuffer')
-        let repo.functions.difftobuffer=s:F.difftobuffer
-    endif
-    if !has_key(repo.functions, 'reltorepo')
-        let repo.functions.reltorepo=s:F.reltorepo
-    endif
-    if !has_key(repo.functions, 'dirty')
-        let repo.functions.dirty=s:F.dirty
-    endif
-    if !has_key(repo.functions, 'getnthparent')
-        let repo.functions.getnthparent=s:F.getnthparent
+        let repo.functions=copy(driver.functions)
     endif
     let repo.diffopts=copy(s:_f.getoption('diffopts'))
     lockvar! repo
             \                'update': s:F.update,
             \           'diffoptslst': s:diffoptslst,
             \           'diffoptsstr': s:diffoptsstr,})
+"▶1 regdriver feature
+let s:requiredfuncs=['repo', 'getcs', 'checkdir']
+let s:optfuncs=['readfile', 'annotate', 'diff', 'status', 'commit', 'update',
+            \   'dirty', 'diffre', 'getstats', 'getrepoprop', 'copy', 'forget']
+"▶2 regdriver :: {f}, name, funcs → + s:drivers
+function s:F.regdriver(plugdict, fdict, name, funcs)
+    "▶3 Check arguments
+    if type(a:name)!=type('') || a:name!~#'\v^\w+$'
+        call s:_f.throw('iname', a:plugdict.id)
+    elseif has_key(s:drivers, a:name)
+        call s:_f.throw('dreg', a:name, a:plugdict.id, s:drivers[a:name].plid)
+    elseif type(a:funcs)!=type({})
+        call s:_f.throw('fndct', a:name, a:plugdic.id)
+    elseif !empty(filter(copy(s:requiredfuncs), '!exists("*a:funcs[v:val]")'))
+        call s:_f.throw('fmis', a:name, a:plugdict.id)
+    elseif !empty(filter(copy(a:funcs), '!exists("*v:val")'))
+        call s:_f.throw('nfun', a:name, a:plugdict.id)
+    endif
+    "▲3
+    let driver={'functions': copy(a:funcs)}
+    let driver.plid=a:plugdict.id
+    let driver.id=a:name
+    call extend(driver.functions, s:deffuncs, 'keep')
+    for funname in filter(copy(s:optfuncs),
+                \         '!exists("*driver.functions[v:val]")')
+        execute      "function driver.functions.".funname."(...)\n".
+                    \"    call s:_f.throw('nimp', '".funname."', ".
+                    \                    "'".a:name."')\n".
+                    \"endfunction"
+    endfor
+    lockvar driver
+    let a:fdict[a:name]=driver
+    let s:drivers[a:name]=driver
+endfunction
+"▶2 deldriver :: {f} → + s:drivers
+function s:F.deldriver(plugdict, fdict)
+    call map(keys(a:fdict), 'remove(s:drivers, v:val)')
+endfunction
+"▶2 Register feature
+call s:_f.newfeature('regdriver', {'cons': s:F.regdriver,
+            \                    'unload': s:F.deldriver})
 "▶1
-call frawor#Lockvar(s:, '_pluginloaded,_r,repos')
+call frawor#Lockvar(s:, '_pluginloaded,_r,repos,drivers')
 " vim: ft=vim ts=4 sts=4 et fmr=▶,▲

File plugin/aurum/status.vim

     execute frawor#Setup('0.0', {'@/resources': '0.0',
                 \            '@aurum/cmdutils': '0.0',
                 \                      '@/fwc': '0.2',
-                \                '@aurum/repo': '0.0',
+                \                '@aurum/repo': '1.0',
                 \                '@aurum/edit': '0.0',
                 \                 '@/commands': '0.0',
                 \                  '@/options': '0.0',