From 8891707e03faf33a15534201a7dbdabdd95e5540 Mon Sep 17 00:00:00 2001 From: David Barnett Date: Fri, 31 Jan 2014 21:05:37 -0800 Subject: [PATCH 1/4] Create maktaba#plugin#Register and refactor Install/GetOrInstall --- autoload/maktaba/plugin.vim | 283 +++++++++++++++++++++++------------- doc/maktaba.txt | 34 +++-- vroom/plugin.vroom | 23 ++- 3 files changed, 221 insertions(+), 119 deletions(-) diff --git a/autoload/maktaba/plugin.vim b/autoload/maktaba/plugin.vim index de89d70..67453b4 100644 --- a/autoload/maktaba/plugin.vim +++ b/autoload/maktaba/plugin.vim @@ -313,6 +313,133 @@ function! maktaba#plugin#CanonicalName(plugin) abort endfunction +"" +" Get a plugin object corresponding to {location}. Returns existing plugin +" object if already registered (but does not register the plugin). +function! s:GetOrCreatePluginObject(location) abort + let l:name = s:PluginNameFromDir(a:location) + if has_key(s:plugins, l:name) + return s:plugins[l:name] + endif + + let l:entrycontroller = { + \ 'autoload': [], + \ 'plugin': [], + \ 'instant': [], + \ 'ftplugin': {} + \} + let l:plugin = { + \ 'name': l:name, + \ 'location': s:Fullpath(a:location), + \ 'flags': {}, + \ 'globals': {}, + \ 'logger': maktaba#log#Logger(l:name), + \ 'Source': function('maktaba#plugin#Source'), + \ 'Load': function('maktaba#plugin#Load'), + \ 'AddonInfo': function('maktaba#plugin#AddonInfo'), + \ 'Flag': function('maktaba#plugin#Flag'), + \ 'HasFlag': function('maktaba#plugin#HasFlag'), + \ 'HasDir': function('maktaba#plugin#HasDir'), + \ 'HasFiletypeData': function('maktaba#plugin#HasFiletypeData'), + \ 'GenerateHelpTags': function('maktaba#plugin#GenerateHelpTags'), + \ 'MapPrefix': function('maktaba#plugin#MapPrefix'), + \ 'IsLibrary': function('maktaba#plugin#IsLibrary'), + \ '_entered': l:entrycontroller, + \ } + " If plugin has an addon-info.json file with a "name" declared, overwrite the + " default name with the custom one. + " Do this after creating the plugin dict so we can call AddonInfo and have + " caching work. + try + let l:addon_info = l:plugin.AddonInfo() + if has_key(l:addon_info, 'name') + let l:plugin.name = l:addon_info.name + endif + catch /ERROR(BadValue):/ + " Couldn't deserialize JSON. + endtry + + return l:plugin +endfunction + + +"" +" Register {plugin} at {location} with maktaba. +" @throws AlreadyExists if the existing plugin comes from a different directory. +function! s:RegisterPlugin(plugin, location) abort + if has_key(s:plugins, a:plugin.name) + let l:orig_plugin = s:plugins[a:plugin.name] + " Compare fully resolved paths. Trailing slashes must (see patch 7.3.194) be + " stripped for resolve(), and fnamemodify() with ':p:h' does this safely. + let l:pluginpath = resolve(fnamemodify(l:orig_plugin.location, ':p:h')) + let l:newpath = resolve(fnamemodify(s:Fullpath(a:location), ':p:h')) + if l:pluginpath !=# l:newpath + let l:msg = 'Conflict for plugin "%s": %s and %s' + throw s:AlreadyExists(l:msg, a:plugin.name, l:pluginpath, l:newpath) + endif + endif + + let s:plugins[a:plugin.name] = a:plugin + let s:plugins_by_location[a:plugin.location] = a:plugin + + " If plugin is symlinked, register resolved path as custom location to avoid + " conflicts. + let l:resolved_location = s:Fullpath(resolve(a:plugin.location)) + if l:resolved_location !=# a:plugin.location + let s:plugins_by_location[l:resolved_location] = a:plugin + endif +endfunction + + +"" +" Install {plugin} on 'runtimepath' if not already listed under either +" {raw_location} or the location value from {plugin}. +function! s:InstallPlugin(plugin, raw_location) abort + let l:rtp_dirs = maktaba#rtp#Split() + " If the plugin location isn't already on the runtimepath, add it. Check + " for both the {raw_location} value and the expanded form. + " Note that this may not detect odd spellings that don't match the raw or + " expanded form, e.g., if it's on rtp with a trailing slash but installed + " using a location without. In such cases, the plugin will end up on the + " runtimepath twice. + if index(l:rtp_dirs, a:raw_location) == -1 && + \ index(l:rtp_dirs, a:plugin.location) == -1 + call maktaba#rtp#Add(a:plugin.location) + endif +endfunction + + +"" +" Perform one-time initialization of {plugin} (load flags, source instant/ +" files, and define g:installed_PLUGIN variable). Apply {settings} to maktaba +" flags. +function! s:InitPlugin(plugin, settings) abort + " These special flags let the user control the loading of parts of the plugin. + if isdirectory(maktaba#path#Join([a:plugin.location, 'plugin'])) + call a:plugin.Flag('plugin', {}) + endif + if isdirectory(maktaba#path#Join([a:plugin.location, 'instant'])) + call a:plugin.Flag('instant', {}) + endif + + " Load flags file first. + call a:plugin.Source(['instant', 'flags'], 1) + " Then apply settings. + if !empty(a:settings) + call s:ApplySettings(a:plugin, a:settings) + endif + " Then load all instant files in random order. + call call('s:SourceDir', ['instant'], a:plugin) + + " g:installed_ is set to signal that the plugin has been installed + " (though perhaps not loaded). This fills the gap between installation time + " (when the plugin is available on the runtimepath) and load time (when the + " plugin's files are sourced). This new convention is expected to make it much + " easier to build vim dependency managers. + let g:installed_{s:SanitizedName(a:plugin.name)} = 1 +endfunction + + "" " @usage dir [settings] " Installs the plugin located at {dir}. Installation entails adding the plugin @@ -367,12 +494,15 @@ endfunction " @throws AlreadyExists if the plugin already exists. " @throws ConfigError if [settings] cannot be applied to this plugin. function! maktaba#plugin#Install(dir, ...) abort - let l:name = s:PluginNameFromDir(a:dir) let l:settings = maktaba#ensure#IsList(get(a:, 1, [])) - if has_key(s:plugins, l:name) - throw s:AlreadyExists('Plugin "%s" already exists.', l:name) + let l:plugin = s:GetOrCreatePluginObject(a:dir) + if has_key(s:plugins, l:plugin.name) + throw s:AlreadyExists('Plugin "%s" already exists.', l:plugin.name) endif - return s:CreatePluginObject(l:name, a:dir, l:settings) + call s:RegisterPlugin(l:plugin, a:dir) + call s:InstallPlugin(l:plugin, a:dir) + call s:InitPlugin(l:plugin, l:settings) + return l:plugin endfunction @@ -404,6 +534,40 @@ function! maktaba#plugin#Get(name) abort endfunction +"" +" Registers the plugin located at {dir} with maktaba, unless it is already +" registered. Should be used for plugins already installed on 'runtimepath'. The +" appropriate maktaba plugin object is returned. +" +" [settings], if given, must be a list of maktaba settings (see +" |maktaba#setting#Create|). If the plugin is new, they will be applied as in +" @function(#Install). Otherwise, they will be applied before returning the +" plugin object. +" +" @throws AlreadyExists if the existing plugin comes from a different directory. +" @throws ConfigError if [settings] cannot be applied to this plugin. +function! maktaba#plugin#Register(dir, ...) abort + let l:settings = maktaba#ensure#IsList(get(a:, 1, [])) + let l:plugin = s:GetOrCreatePluginObject(a:dir) + if has_key(s:plugins, l:plugin.name) + " Plugin name already registered. Register again to trigger path check (and + " to store a:dir location if that path style wasn't encountered yet. + call s:RegisterPlugin(l:plugin, a:dir) + if !empty(l:settings) + call s:ApplySettings(l:plugin, l:settings) + endif + return l:plugin + endif + + " Plugin name hasn't been registered yet. + call s:RegisterPlugin(l:plugin, a:dir) + " NOTE: Does not check explicitly if plugin is already on runtimepath. If it's + " not, instant/ files may get sourced without autoload functions available. + call s:InitPlugin(l:plugin, l:settings) + return l:plugin +endfunction + + "" " Installs the plugin located at {dir}, unless it already exists. The " appropriate maktaba plugin object is returned. @@ -417,24 +581,23 @@ endfunction " @throws AlreadyExists if the existing plugin comes from a different directory. " @throws ConfigError if [settings] cannot be applied to this plugin. function! maktaba#plugin#GetOrInstall(dir, ...) abort - let l:name = s:PluginNameFromDir(a:dir) let l:settings = maktaba#ensure#IsList(get(a:, 1, [])) - if has_key(s:plugins, l:name) - let l:plugin = s:plugins[l:name] - " Compare fully resolved paths. Trailing slashes must (see patch 7.3.194) be - " stripped for resolve(), and fnamemodify() with ':p:h' does this safely. - let l:pluginpath = fnamemodify(l:plugin.location, ':p:h') - let l:newpath = s:Fullpath(a:dir) - if resolve(l:pluginpath) !=# resolve(fnamemodify(l:newpath, ':p:h')) - let l:msg = 'Conflict for plugin "%s": %s and %s' - throw s:AlreadyExists(l:msg, l:plugin.name, l:plugin.location, l:newpath) - endif + let l:plugin = s:GetOrCreatePluginObject(a:dir) + if has_key(s:plugins, l:plugin.name) + " Plugin name already registered. Register again to trigger path check (and + " to store a:dir location if that path style wasn't encountered yet. + call s:RegisterPlugin(l:plugin, a:dir) if !empty(l:settings) call s:ApplySettings(l:plugin, l:settings) endif return l:plugin endif - return s:CreatePluginObject(l:name, a:dir, l:settings) + + " Plugin name hasn't been registered yet. + call s:RegisterPlugin(l:plugin, a:dir) + call s:InstallPlugin(l:plugin, a:dir) + call s:InitPlugin(l:plugin, l:settings) + return l:plugin endfunction @@ -444,94 +607,6 @@ endfunction " itself. -" Common code used by #Install and #GetOrInstall. -function! s:CreatePluginObject(name, location, settings) abort - let l:entrycontroller = { - \ 'autoload': [], - \ 'plugin': [], - \ 'instant': [], - \ 'ftplugin': {} - \} - let l:plugin = { - \ 'name': a:name, - \ 'location': s:Fullpath(a:location), - \ 'flags': {}, - \ 'globals': {}, - \ 'logger': maktaba#log#Logger(a:name), - \ 'Source': function('maktaba#plugin#Source'), - \ 'Load': function('maktaba#plugin#Load'), - \ 'AddonInfo': function('maktaba#plugin#AddonInfo'), - \ 'Flag': function('maktaba#plugin#Flag'), - \ 'HasFlag': function('maktaba#plugin#HasFlag'), - \ 'HasDir': function('maktaba#plugin#HasDir'), - \ 'HasFiletypeData': function('maktaba#plugin#HasFiletypeData'), - \ 'GenerateHelpTags': function('maktaba#plugin#GenerateHelpTags'), - \ 'MapPrefix': function('maktaba#plugin#MapPrefix'), - \ 'IsLibrary': function('maktaba#plugin#IsLibrary'), - \ '_entered': l:entrycontroller, - \ } - " If plugin has an addon-info.json file with a "name" declared, overwrite the - " default name with the custom one. - " Do this after creating the plugin dict so we can call AddonInfo and have - " caching work. - try - let l:addon_info = l:plugin.AddonInfo() - if has_key(l:addon_info, 'name') - let l:plugin.name = l:addon_info.name - endif - catch /ERROR(BadValue):/ - " Couldn't deserialize JSON. - endtry - let s:plugins[l:plugin.name] = l:plugin - let s:plugins_by_location[l:plugin.location] = l:plugin - - " If plugin is symlinked, register resolved path as custom location to avoid - " conflicts. - let l:resolved_location = s:Fullpath(resolve(l:plugin.location)) - if l:resolved_location !=# l:plugin.location - let s:plugins_by_location[l:resolved_location] = l:plugin - endif - - let l:rtp_dirs = maktaba#rtp#Split() - " If the plugin location isn't already on the runtimepath, add it. Check - " for both the raw {location} value and the expanded form. - " Note that this may not detect odd spellings that don't match the raw or - " expanded form, e.g., if it's on rtp with a trailing slash but installed - " using a location without. In such cases, the plugin will end up on the - " runtimepath twice. - if index(l:rtp_dirs, a:location) == -1 && - \ index(l:rtp_dirs, l:plugin.location) == -1 - call maktaba#rtp#Add(l:plugin.location) - endif - - " These special flags let the user control the loading of parts of the plugin. - if isdirectory(maktaba#path#Join([l:plugin.location, 'plugin'])) - call l:plugin.Flag('plugin', {}) - endif - if isdirectory(maktaba#path#Join([l:plugin.location, 'instant'])) - call l:plugin.Flag('instant', {}) - endif - - " Load flags file first. - call l:plugin.Source(['instant', 'flags'], 1) - " Then apply settings. - if !empty(a:settings) - call s:ApplySettings(l:plugin, a:settings) - endif - " Then load all instant files in random order. - call call('s:SourceDir', ['instant'], l:plugin) - - " g:installed_ is set to signal that the plugin has been installed - " (though perhaps not loaded). This fills the gap between installation time - " (when the plugin is available on the runtimepath) and load time (when the - " plugin's files are sourced). This new convention is expected to make it much - " easier to build vim dependency managers. - let g:installed_{s:SanitizedName(l:plugin.name)} = 1 - - return l:plugin -endfunction - - " @dict Plugin " Gets a list of all subdirectories in the root plugin directory. " Caches the list for performance, so new paths will not be discovered after the diff --git a/doc/maktaba.txt b/doc/maktaba.txt index a2aff0e..fc0e7e5 100644 --- a/doc/maktaba.txt +++ b/doc/maktaba.txt @@ -1053,16 +1053,12 @@ maktaba#plugin#IsRegistered({plugin}) *maktaba#plugin#IsRegistered* maktaba#plugin#CanonicalName({plugin}) *maktaba#plugin#CanonicalName* The canonical name of {plugin}. This is the name of the plugin directory - with all invalid characters replaced with underscores. Valid characters - include _, [a-z], [A-Z], and [0-9]. For example, the canonical name of - "my-plugin" is "my_plugin". Certain conventions which are common for github - vim projects are also recognized. Specifically, either a "vim-" prefix and a - ".vim" suffix would be disregarded: both "vim-unimpaired" and - "unimpaired.vim" would become simply "unimpaired". + with any "vim-" prefix or ".vim" suffix stripped off: both "vim-unimpaired" + and "unimpaired.vim" would become simply "unimpaired". Note that plugins with different names in the filesystem can conflict in - maktaba. If you've loaded a plugin in the directory "plugins/my-plugin" then - maktaba can't handle a plugin named "plugins/my_plugin". Make sure your + maktaba. If you've loaded a plugin in the directory "plugins/vim-myplugin" + then maktaba can't handle a plugin named "plugins/myplugin". Make sure your plugins have sufficiently different names! maktaba#plugin#Install({dir}, [settings]) *maktaba#plugin#Install* @@ -1121,12 +1117,26 @@ maktaba#plugin#Install({dir}, [settings]) *maktaba#plugin#Install* maktaba#plugin#Get({plugin}) *maktaba#plugin#Get* Gets the plugin object associated with {plugin}. {plugin} may either be the - name of the plugin directory, or the canonicalized plugin name (with invalid - characters converted to underscores). See |maktaba#plugin#CanonicalName|. - Detects plugins added to 'runtimepath' even if they haven't been explicitly - registered with maktaba. + name of the plugin directory, or the canonicalized plugin name (with any + "vim-" prefix or ".vim" suffix stripped off). See + |maktaba#plugin#CanonicalName|. Detects plugins added to 'runtimepath' even + if they haven't been explicitly registered with maktaba. Throws ERROR(NotFound) if the plugin object does not exist. +maktaba#plugin#Register({dir}, [settings]) *maktaba#plugin#Register* + Registers the plugin located at {dir} with maktaba, unless it is already + registered. Should be used for plugins already installed on 'runtimepath'. + The appropriate maktaba plugin object is returned. + + [settings], if given, must be a list of maktaba settings (see + |maktaba#setting#Create|). If the plugin is new, they will be applied as in + |maktaba#plugin#Install|. Otherwise, they will be applied before returning + the plugin object. + + Throws ERROR(AlreadyExists) if the existing plugin comes from a different + directory. + Throws ERROR(ConfigError) if [settings] cannot be applied to this plugin. + maktaba#plugin#GetOrInstall({dir}, [settings]) *maktaba#plugin#GetOrInstall* Installs the plugin located at {dir}, unless it already exists. The appropriate maktaba plugin object is returned. diff --git a/vroom/plugin.vroom b/vroom/plugin.vroom index 042e113..3627b53 100644 --- a/vroom/plugin.vroom +++ b/vroom/plugin.vroom @@ -148,7 +148,7 @@ Now, we install the plugin by giving maktaba the full plugin path: This should generally be done by a plugin manager. A few things have happened. First of all, notice that the flags file was sourced -immediately. (Take a look at fakeplugins/myplugin/plugin/flags.vim to see where +immediately. (Take a look at fakeplugins/myplugin/instant/flags.vim to see where the message came from.) Secondly and most importantly, our plugin is now on the runtimepath and registered with maktaba: @@ -160,6 +160,22 @@ maktaba#plugin#Get to get another: :call maktaba#ensure#IsEqual(maktaba#plugin#Get('myplugin'), g:plugin) +Non-plugin-manager code may also want to ensure that a plugin is registered with +maktaba (and has flags initialized and instant/ code executed) without actually +affecting runtimepath or triggering other side-effects that should be left to +the plugin manager. For these cases, there's maktaba#plugin#Register. + + @messages (STRICT) + :let g:modularpluginpath = maktaba#path#Join( + | [g:thisdir, 'fakeplugins', 'modularplugin']) + :let g:modularplugin = maktaba#plugin#Register(g:modularpluginpath) + ~ The flags file is sourced immediately. + @messages + +Notice that this plugin hasn't been added to runtimepath. + + :call maktaba#ensure#IsFalse(has_key(maktaba#rtp#LeafDirs(), 'modularplugin')) + This all works as you might expect, but none of it is very interesting. Let's see what the plugin object can do. It posesses some boring data, such as name and location: @@ -208,7 +224,8 @@ Call maktaba#plugin#Detect to detect and register plugins. :call maktaba#plugin#Detect() ~ INSTANT LOADED :echomsg string(maktaba#plugin#RegisteredPlugins()) - ~ ['emptyplugin', 'fullplugin', 'loudplugin', 'maktaba', 'myplugin'] + ~ ['emptyplugin', 'fullplugin', 'loudplugin', 'maktaba', 'modularplugin', + | 'myplugin'] Calling maktaba#plugin#RegisteredPlugins will also detect new plugins automatically. @@ -218,7 +235,7 @@ automatically. :echomsg string(maktaba#plugin#RegisteredPlugins()) ~ ['emptyplugin', 'fullplugin', 'library', 'library2', 'loudplugin', - | 'maktaba', 'myplugin'] + | 'maktaba', 'modularplugin', 'myplugin'] That's Maktaba plugin basics for you. There's still a lot more ground to cover. Here's a directory of relevant topics: From 186baaa7083935f79e2442afb4db9bb7878a3079 Mon Sep 17 00:00:00 2001 From: David Barnett Date: Tue, 11 Feb 2014 00:51:30 -0800 Subject: [PATCH 2/4] Change #plugin#GetOrInstall calls to use #plugin#Register instead --- autoload/maktaba.vim | 2 +- autoload/maktaba/plugin.vim | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/autoload/maktaba.vim b/autoload/maktaba.vim index db8d19c..eb5392e 100644 --- a/autoload/maktaba.vim +++ b/autoload/maktaba.vim @@ -21,7 +21,7 @@ " :p:h:h is .../maktaba/ let s:plugindir = expand(':p:h:h') if !exists('s:maktaba') - let s:maktaba = maktaba#plugin#GetOrInstall(s:plugindir) + let s:maktaba = maktaba#plugin#Register(s:plugindir) let s:maktaba.globals.installers = [] let s:maktaba.globals.loghandlers = maktaba#reflist#Create() endif diff --git a/autoload/maktaba/plugin.vim b/autoload/maktaba/plugin.vim index 67453b4..dc1385d 100644 --- a/autoload/maktaba/plugin.vim +++ b/autoload/maktaba/plugin.vim @@ -4,8 +4,8 @@ if !exists('s:plugins') endif " Mapping from normalized locations to the corresponding plugin object. -" Used to look up plugins by location in maktaba#plugin#Install and -" maktaba#plugin#GetOrInstall. +" Used to look up plugins by location in @function(#Install), +" @function(#Register), and @function(#GetOrInstall). " May have multiple locations mapped to the same plugin in the case of symlinks. if !exists('s:plugins_by_location') let s:plugins_by_location = {} @@ -219,7 +219,7 @@ endfunction " g:loaded_* variable when appropriate. function! maktaba#plugin#Enter(file) abort let [l:plugindir, l:filedir, l:handle] = s:SplitEnteredFile(a:file) - let l:plugin = maktaba#plugin#GetOrInstall(l:plugindir) + let l:plugin = maktaba#plugin#Register(l:plugindir) let l:controller = l:plugin._entered[l:filedir] if l:filedir ==# 'ftplugin' @@ -260,7 +260,7 @@ endfunction " maktaba. May trigger instant/ hooks for newly-registered plugins. function! maktaba#plugin#Detect() abort for [l:name, l:location] in items(maktaba#rtp#LeafDirs()) - call maktaba#plugin#GetOrInstall(l:location) + call maktaba#plugin#Register(l:location) endfor endfunction @@ -527,7 +527,7 @@ function! maktaba#plugin#Get(name) abort " Check if any dir on runtimepath is a plugin that hasn't been detected yet. let l:leafdirs = maktaba#rtp#LeafDirs() if has_key(l:leafdirs, a:name) - return maktaba#plugin#GetOrInstall(l:leafdirs[a:name]) + return maktaba#plugin#Register(l:leafdirs[a:name]) endif throw maktaba#error#NotFound('Plugin %s', a:name) From 7b25c31c1769576802ff49baf1ca88392c0d4a1a Mon Sep 17 00:00:00 2001 From: David Barnett Date: Tue, 11 Feb 2014 21:37:54 -0800 Subject: [PATCH 3/4] Merge script-local Register/Install/Init functions to clarify logic --- autoload/maktaba/plugin.vim | 292 +++++++++++++++++------------------- 1 file changed, 135 insertions(+), 157 deletions(-) diff --git a/autoload/maktaba/plugin.vim b/autoload/maktaba/plugin.vim index dc1385d..1e16e82 100644 --- a/autoload/maktaba/plugin.vim +++ b/autoload/maktaba/plugin.vim @@ -313,133 +313,6 @@ function! maktaba#plugin#CanonicalName(plugin) abort endfunction -"" -" Get a plugin object corresponding to {location}. Returns existing plugin -" object if already registered (but does not register the plugin). -function! s:GetOrCreatePluginObject(location) abort - let l:name = s:PluginNameFromDir(a:location) - if has_key(s:plugins, l:name) - return s:plugins[l:name] - endif - - let l:entrycontroller = { - \ 'autoload': [], - \ 'plugin': [], - \ 'instant': [], - \ 'ftplugin': {} - \} - let l:plugin = { - \ 'name': l:name, - \ 'location': s:Fullpath(a:location), - \ 'flags': {}, - \ 'globals': {}, - \ 'logger': maktaba#log#Logger(l:name), - \ 'Source': function('maktaba#plugin#Source'), - \ 'Load': function('maktaba#plugin#Load'), - \ 'AddonInfo': function('maktaba#plugin#AddonInfo'), - \ 'Flag': function('maktaba#plugin#Flag'), - \ 'HasFlag': function('maktaba#plugin#HasFlag'), - \ 'HasDir': function('maktaba#plugin#HasDir'), - \ 'HasFiletypeData': function('maktaba#plugin#HasFiletypeData'), - \ 'GenerateHelpTags': function('maktaba#plugin#GenerateHelpTags'), - \ 'MapPrefix': function('maktaba#plugin#MapPrefix'), - \ 'IsLibrary': function('maktaba#plugin#IsLibrary'), - \ '_entered': l:entrycontroller, - \ } - " If plugin has an addon-info.json file with a "name" declared, overwrite the - " default name with the custom one. - " Do this after creating the plugin dict so we can call AddonInfo and have - " caching work. - try - let l:addon_info = l:plugin.AddonInfo() - if has_key(l:addon_info, 'name') - let l:plugin.name = l:addon_info.name - endif - catch /ERROR(BadValue):/ - " Couldn't deserialize JSON. - endtry - - return l:plugin -endfunction - - -"" -" Register {plugin} at {location} with maktaba. -" @throws AlreadyExists if the existing plugin comes from a different directory. -function! s:RegisterPlugin(plugin, location) abort - if has_key(s:plugins, a:plugin.name) - let l:orig_plugin = s:plugins[a:plugin.name] - " Compare fully resolved paths. Trailing slashes must (see patch 7.3.194) be - " stripped for resolve(), and fnamemodify() with ':p:h' does this safely. - let l:pluginpath = resolve(fnamemodify(l:orig_plugin.location, ':p:h')) - let l:newpath = resolve(fnamemodify(s:Fullpath(a:location), ':p:h')) - if l:pluginpath !=# l:newpath - let l:msg = 'Conflict for plugin "%s": %s and %s' - throw s:AlreadyExists(l:msg, a:plugin.name, l:pluginpath, l:newpath) - endif - endif - - let s:plugins[a:plugin.name] = a:plugin - let s:plugins_by_location[a:plugin.location] = a:plugin - - " If plugin is symlinked, register resolved path as custom location to avoid - " conflicts. - let l:resolved_location = s:Fullpath(resolve(a:plugin.location)) - if l:resolved_location !=# a:plugin.location - let s:plugins_by_location[l:resolved_location] = a:plugin - endif -endfunction - - -"" -" Install {plugin} on 'runtimepath' if not already listed under either -" {raw_location} or the location value from {plugin}. -function! s:InstallPlugin(plugin, raw_location) abort - let l:rtp_dirs = maktaba#rtp#Split() - " If the plugin location isn't already on the runtimepath, add it. Check - " for both the {raw_location} value and the expanded form. - " Note that this may not detect odd spellings that don't match the raw or - " expanded form, e.g., if it's on rtp with a trailing slash but installed - " using a location without. In such cases, the plugin will end up on the - " runtimepath twice. - if index(l:rtp_dirs, a:raw_location) == -1 && - \ index(l:rtp_dirs, a:plugin.location) == -1 - call maktaba#rtp#Add(a:plugin.location) - endif -endfunction - - -"" -" Perform one-time initialization of {plugin} (load flags, source instant/ -" files, and define g:installed_PLUGIN variable). Apply {settings} to maktaba -" flags. -function! s:InitPlugin(plugin, settings) abort - " These special flags let the user control the loading of parts of the plugin. - if isdirectory(maktaba#path#Join([a:plugin.location, 'plugin'])) - call a:plugin.Flag('plugin', {}) - endif - if isdirectory(maktaba#path#Join([a:plugin.location, 'instant'])) - call a:plugin.Flag('instant', {}) - endif - - " Load flags file first. - call a:plugin.Source(['instant', 'flags'], 1) - " Then apply settings. - if !empty(a:settings) - call s:ApplySettings(a:plugin, a:settings) - endif - " Then load all instant files in random order. - call call('s:SourceDir', ['instant'], a:plugin) - - " g:installed_ is set to signal that the plugin has been installed - " (though perhaps not loaded). This fills the gap between installation time - " (when the plugin is available on the runtimepath) and load time (when the - " plugin's files are sourced). This new convention is expected to make it much - " easier to build vim dependency managers. - let g:installed_{s:SanitizedName(a:plugin.name)} = 1 -endfunction - - "" " @usage dir [settings] " Installs the plugin located at {dir}. Installation entails adding the plugin @@ -499,9 +372,7 @@ function! maktaba#plugin#Install(dir, ...) abort if has_key(s:plugins, l:plugin.name) throw s:AlreadyExists('Plugin "%s" already exists.', l:plugin.name) endif - call s:RegisterPlugin(l:plugin, a:dir) - call s:InstallPlugin(l:plugin, a:dir) - call s:InitPlugin(l:plugin, l:settings) + call s:RegisterPlugin(l:plugin, a:dir, l:settings, 1) return l:plugin endfunction @@ -549,21 +420,7 @@ endfunction function! maktaba#plugin#Register(dir, ...) abort let l:settings = maktaba#ensure#IsList(get(a:, 1, [])) let l:plugin = s:GetOrCreatePluginObject(a:dir) - if has_key(s:plugins, l:plugin.name) - " Plugin name already registered. Register again to trigger path check (and - " to store a:dir location if that path style wasn't encountered yet. - call s:RegisterPlugin(l:plugin, a:dir) - if !empty(l:settings) - call s:ApplySettings(l:plugin, l:settings) - endif - return l:plugin - endif - - " Plugin name hasn't been registered yet. - call s:RegisterPlugin(l:plugin, a:dir) - " NOTE: Does not check explicitly if plugin is already on runtimepath. If it's - " not, instant/ files may get sourced without autoload functions available. - call s:InitPlugin(l:plugin, l:settings) + call s:RegisterPlugin(l:plugin, a:dir, l:settings, 0) return l:plugin endfunction @@ -583,24 +440,145 @@ endfunction function! maktaba#plugin#GetOrInstall(dir, ...) abort let l:settings = maktaba#ensure#IsList(get(a:, 1, [])) let l:plugin = s:GetOrCreatePluginObject(a:dir) - if has_key(s:plugins, l:plugin.name) - " Plugin name already registered. Register again to trigger path check (and - " to store a:dir location if that path style wasn't encountered yet. - call s:RegisterPlugin(l:plugin, a:dir) - if !empty(l:settings) - call s:ApplySettings(l:plugin, l:settings) - endif - return l:plugin + call s:RegisterPlugin(l:plugin, a:dir, l:settings, 1) + return l:plugin +endfunction + + +"" +" Get a plugin object corresponding to {location}. Returns existing plugin +" object if already registered (but does not register the plugin). +function! s:GetOrCreatePluginObject(location) abort + let l:name = s:PluginNameFromDir(a:location) + if has_key(s:plugins, l:name) + return s:plugins[l:name] endif - " Plugin name hasn't been registered yet. - call s:RegisterPlugin(l:plugin, a:dir) - call s:InstallPlugin(l:plugin, a:dir) - call s:InitPlugin(l:plugin, l:settings) + let l:entrycontroller = { + \ 'autoload': [], + \ 'plugin': [], + \ 'instant': [], + \ 'ftplugin': {} + \} + let l:plugin = { + \ 'name': l:name, + \ 'location': s:Fullpath(a:location), + \ 'flags': {}, + \ 'globals': {}, + \ 'logger': maktaba#log#Logger(l:name), + \ 'Source': function('maktaba#plugin#Source'), + \ 'Load': function('maktaba#plugin#Load'), + \ 'AddonInfo': function('maktaba#plugin#AddonInfo'), + \ 'Flag': function('maktaba#plugin#Flag'), + \ 'HasFlag': function('maktaba#plugin#HasFlag'), + \ 'HasDir': function('maktaba#plugin#HasDir'), + \ 'HasFiletypeData': function('maktaba#plugin#HasFiletypeData'), + \ 'GenerateHelpTags': function('maktaba#plugin#GenerateHelpTags'), + \ 'MapPrefix': function('maktaba#plugin#MapPrefix'), + \ 'IsLibrary': function('maktaba#plugin#IsLibrary'), + \ '_entered': l:entrycontroller, + \ } + " If plugin has an addon-info.json file with a "name" declared, overwrite the + " default name with the custom one. + " Do this after creating the plugin dict so we can call AddonInfo and have + " caching work. + try + let l:addon_info = l:plugin.AddonInfo() + if has_key(l:addon_info, 'name') + let l:plugin.name = l:addon_info.name + endif + catch /ERROR(BadValue):/ + " Couldn't deserialize JSON. + endtry + return l:plugin endfunction +"" +" Register {plugin} at {location} with maktaba, applying {settings}. If +" {force_rtp} is 1, the plugin's location will be added to 'runtimepath' if it's +" not detected there already. +" @throws AlreadyExists if the existing plugin comes from a different directory. +function! s:RegisterPlugin(plugin, location, settings, force_rtp) abort + let l:already_installed = has_key(s:plugins, a:plugin.name) + if l:already_installed + let l:orig_plugin = s:plugins[a:plugin.name] + " Compare fully resolved paths. Trailing slashes must (see patch 7.3.194) be + " stripped for resolve(), and fnamemodify() with ':p:h' does this safely. + let l:pluginpath = resolve(fnamemodify(l:orig_plugin.location, ':p:h')) + let l:newpath = resolve(fnamemodify(s:Fullpath(a:location), ':p:h')) + if l:pluginpath !=# l:newpath + let l:msg = 'Conflict for plugin "%s": %s and %s' + throw s:AlreadyExists(l:msg, a:plugin.name, l:pluginpath, l:newpath) + endif + endif + + let s:plugins[a:plugin.name] = a:plugin + let s:plugins_by_location[a:plugin.location] = a:plugin + + " If plugin is symlinked, register resolved path as custom location to avoid + " conflicts. + let l:resolved_location = s:Fullpath(resolve(a:plugin.location)) + if l:resolved_location !=# a:plugin.location + let s:plugins_by_location[l:resolved_location] = a:plugin + endif + + if l:already_installed + if !empty(a:settings) + call s:ApplySettings(a:plugin, a:settings) + endif + else + if a:force_rtp + let l:rtp_dirs = maktaba#rtp#Split() + " If the plugin location isn't already on the runtimepath, add it. Check + " for both the {raw_location} value and the expanded form. + " Note that this may not detect odd spellings that don't match the raw or + " expanded form, e.g., if it's on rtp with a trailing slash but installed + " using a location without. In such cases, the plugin will end up on the + " runtimepath twice. + if index(l:rtp_dirs, a:location) == -1 && + \ index(l:rtp_dirs, a:plugin.location) == -1 + call maktaba#rtp#Add(a:plugin.location) + endif + endif + + call s:InitPlugin(a:plugin, a:settings) + endif +endfunction + + +"" +" Perform one-time initialization of {plugin} (load flags, source instant/ +" files, and define g:installed_PLUGIN variable). Apply {settings} to maktaba +" flags. +function! s:InitPlugin(plugin, settings) abort + " These special flags let the user control the loading of parts of the plugin. + if isdirectory(maktaba#path#Join([a:plugin.location, 'plugin'])) + call a:plugin.Flag('plugin', {}) + endif + if isdirectory(maktaba#path#Join([a:plugin.location, 'instant'])) + call a:plugin.Flag('instant', {}) + endif + + " Load flags file first. + call a:plugin.Source(['instant', 'flags'], 1) + " Then apply settings. + if !empty(a:settings) + call s:ApplySettings(a:plugin, a:settings) + endif + " Then load all instant files in random order. + call call('s:SourceDir', ['instant'], a:plugin) + + " g:installed_ is set to signal that the plugin has been installed + " (though perhaps not loaded). This fills the gap between installation time + " (when the plugin is available on the runtimepath) and load time (when the + " plugin's files are sourced). This new convention is expected to make it much + " easier to build vim dependency managers. + let g:installed_{s:SanitizedName(a:plugin.name)} = 1 +endfunction + + "" " @dict Plugin " The maktaba plugin object. Exposes functions that operate on the plugin From 23402d0f8008b726aa6a528eb0ad5944d46c6963 Mon Sep 17 00:00:00 2001 From: David Barnett Date: Sun, 16 Feb 2014 14:52:49 -0800 Subject: [PATCH 4/4] Fix another instance of resolve() bug and consolidate code --- autoload/maktaba/plugin.vim | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/autoload/maktaba/plugin.vim b/autoload/maktaba/plugin.vim index 1e16e82..0ef7197 100644 --- a/autoload/maktaba/plugin.vim +++ b/autoload/maktaba/plugin.vim @@ -45,6 +45,16 @@ function! s:CannotEnter(file) abort endfunction +"" +" Gets resolved version of {path}, expanding symlinks. +" Works the same as |resolve()|, but doesn't choke on paths with trailing +" slashes under vim<7.3.194. +function! s:Resolve(path) abort + " Remove trailing slash if there is one, then resolve symlinks. + return resolve(fnamemodify(a:path, ':p:h')) +endfunction + + " This is The Way to store a plugin location, by convention: " Fully expanded path with trailing slash at the end. function! s:Fullpath(location) abort @@ -504,10 +514,9 @@ function! s:RegisterPlugin(plugin, location, settings, force_rtp) abort let l:already_installed = has_key(s:plugins, a:plugin.name) if l:already_installed let l:orig_plugin = s:plugins[a:plugin.name] - " Compare fully resolved paths. Trailing slashes must (see patch 7.3.194) be - " stripped for resolve(), and fnamemodify() with ':p:h' does this safely. - let l:pluginpath = resolve(fnamemodify(l:orig_plugin.location, ':p:h')) - let l:newpath = resolve(fnamemodify(s:Fullpath(a:location), ':p:h')) + " Compare fully resolved paths. + let l:pluginpath = s:Resolve(l:orig_plugin.location) + let l:newpath = s:Resolve(s:Fullpath(a:location)) if l:pluginpath !=# l:newpath let l:msg = 'Conflict for plugin "%s": %s and %s' throw s:AlreadyExists(l:msg, a:plugin.name, l:pluginpath, l:newpath) @@ -519,7 +528,7 @@ function! s:RegisterPlugin(plugin, location, settings, force_rtp) abort " If plugin is symlinked, register resolved path as custom location to avoid " conflicts. - let l:resolved_location = s:Fullpath(resolve(a:plugin.location)) + let l:resolved_location = s:Fullpath(s:Resolve(a:plugin.location)) if l:resolved_location !=# a:plugin.location let s:plugins_by_location[l:resolved_location] = a:plugin endif