Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- #==============================================================================#
- # Plugin Manager #
- # by Marin #
- #------------------------------------------------------------------------------#
- # Provides a simple interface that allows plugins to require dependencies #
- # at specific versions, and to specify incompatibilities between plugins. #
- #------------------------------------------------------------------------------#
- # Usage #
- # #
- # Any Pokémon Essentials plugin should register itself using the PluginManager.#
- # The simplest way to do so, for a plugin without dependencies, is as follows: #
- # PluginManager.register({ #
- # :name => "Basic Plugin", #
- # :version => "1.0", #
- # :link => "https://reliccastle.com/link-to-the-plugin/ #
- # }) #
- # The link portion here is optional, but recommended. This will be linked if #
- # the PluginManager detects that this plugin needs to be updated. #
- # #
- # A plugin's version is typically in the format X.Y.Z, but the number of #
- # digits does not matter. You can also use Xa, Xb, Xc, Ya, etc. #
- # What matters is that you use it consistently, so that it can be compared. #
- # #
- # #
- # #
- # Now let's say we create a new plugin titled "Simple Extension", which #
- # requires our previously created "Basic Plugin" to work. #
- # PluginManager.register({ #
- # :name => "Simple Extension", #
- # :version => "1.0", #
- # :link => "https://reliccastle.com/link-to-the-plugin/, #
- # :dependencies => [ #
- # "Basic Plugin" #
- # ] #
- # }) #
- # This will ensure that Basic Plugin is installed, ignoring the version. #
- # If you have only one dependency, you can shorten it: #
- # :dependencies => "Basic Plugin" #
- # #
- # #
- # #
- # To enforce upwards from a specific version, you turn the dependency into an #
- # array, containing the name and the version. For instance, let's say we need #
- # Basic Plugin to be at least version 1.2. #
- # PluginManager.register({ #
- # :name => "Simple Extension", #
- # :version => "1.0", #
- # :link => "https://reliccastle.com/link-to-the-plugin/, #
- # :dependencies => [ #
- # ["Basic Plugin", "1.2"] #
- # ] #
- # }) #
- # Now Basic Plugin is required to be version 1.2 or above. #
- # #
- # #
- # #
- # If we want to target one specific version of a plugin, meaning no newer than #
- # what we specify, we have to add an :exact flag. #
- # PluginManager.register({ #
- # :name => "Simple Extension", #
- # :version => "1.0", #
- # :link => "https://reliccastle.com/link-to-the-plugin/, #
- # :dependencies => [ #
- # [:exact, "Basic Plugin", "1.2" #
- # ] #
- # }) #
- # This requires Basic Plugin to be exactly version 1.2; no older, no newer. #
- # #
- # #
- # #
- # Let's say we create a new plugin, "QoL Improvements", but it doesn't work #
- # with the Simple Extension plugin. This can happen for two pause menu plugins,#
- # for instance. Then QoL Improvements is incompatible with Simple Extension. #
- # PluginManager.register({ #
- # :name => "QoL Improvements", #
- # :version => "1.0", #
- # :link => "https://reliccastle.com/link-to-the-plugin/, #
- # :incompatibilities => [ #
- # "Simple Extension" #
- # ] #
- # }) #
- # At least one of the plugins must have the incompatibilities field set for it #
- # to work. Now these two plugins can never be used together. #
- # #
- # #
- # #
- # If you have a plugin that works with a different plugin, but that plugin is #
- # not required for it to work, you can specify an :optional flag. #
- # If a dependency has this :optional flag, it will be ignored if the dependency#
- # is not present, but if it is, it will test it against the specified version. #
- # :name => "Other Plugin", #
- # :version => "1.0", #
- # :link => "https://reliccastle.com/link-to-the-plugin/, #
- # :dependencies => [ #
- # [:optional, "QoL Improvements", "1.1"] #
- # ] #
- # }) #
- # Now if QoL Improvements is installed, Other Plugin will require it to be #
- # version 1.1 of the plugin, or later. If you need an exact match like the #
- # :exact flag would provide, you would use :optional_exact. #
- #------------------------------------------------------------------------------#
- # Please give credit when using this. #
- #==============================================================================#
- module PluginManager
- # Win32API MessageBox function for custom errors
- MBOX = Win32API.new('user32', 'MessageBox', ['I','P','P','I'], 'I')
- # Holds all registered plugin data
- @@Plugins = {}
- # Registers a plugin and tests its dependencies and incompatibilities.
- def self.register(options)
- name = nil
- version = nil
- link = nil
- dependencies = nil
- order = [:name, :version, :link, :dependencies, :incompatibilities]
- # Ensure it first reads name, as it's used in error reporting
- # by sorting the keys.
- keys = options.keys.sort { |a, b| order.index(a) || order.size <=> order.index(b) || order.size }
- for key in keys
- value = options[key]
- case key
- when :name
- # Plugin name
- if nil_or_empty(value)
- self.error "Plugin name must be a non-empty string."
- end
- if !@@Plugins[value].nil?
- self.error "Plugin by the name of '#{value}' already exists."
- end
- name = value
- when :version
- # Plugin version
- if nil_or_empty(value)
- self.error "Plugin version must be a string."
- end
- version = value
- when :link
- # Plugin link
- if nil_or_empty(value)
- self.error "Plugin link must be a string."
- end
- link = value
- when :dependencies
- # Plugin dependencies
- dependencies = value
- dependencies = [dependencies] if !dependencies.is_a?(Array) || !dependencies[0].is_a?(Array)
- for dep in value
- if dep.is_a?(String)
- # Just one string
- if !self.installed?(dep)
- # Check if generally installed
- self.error "Plugin '#{name}' requires plugin '#{dep}' to be installed.\n" +
- "Ensure that the dependency is above this plugin in the script list."
- end
- elsif dep.is_a?(Array)
- if dep.size == 1
- # Just one string, plugin name
- if dep[0].is_a?(String)
- dep_name = dep[0]
- # Check if generally installed
- if !self.installed?(dep_name)
- self.error "Plugin '#{name}' requires plugin '#{dep_name}' to be installed.\n" +
- "Ensure that the dependency is above this plugin in the script list."
- end
- else
- self.error "Expected the plugin name as a string, but got #{dep[0].inspect}."
- end
- elsif dep.size == 2
- # Name and version
- if dep[0].is_a?(Symbol)
- # Flags are only supported with a version specified.
- self.error "Cannot specify dependency version check modifier without specifying a version."
- elsif dep[0].is_a?(String) && dep[1].is_a?(String)
- # name and version
- dep_name = dep[0]
- dep_version = dep[1]
- # If installed at specified version, ignore
- if self.installed?(dep_name, dep_version)
- # If installed, but not at the required version
- elsif self.installed?(dep_name)
- # Needs update
- msg = "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} or later, " +
- "but the installed version is #{self.version(dep_name)}."
- if dep_link = self.link(dep_name)
- msg += "\n\nThe plugin may be updated from: #{dep_link}"
- end
- self.error msg
- # Not installed at all
- else
- self.error "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} or later " +
- "to be installed.\nEnsure that the dependency is above this plugin in the script list."
- end
- end
- elsif dep.size == 3
- # Flag, name and version
- if !dep[0].is_a?(Symbol)
- self.error "Expected first dependency argument to be a symbol, but got #{dep[0].inspect}."
- end
- if !dep[1].is_a?(String)
- self.error "Expected second dependency argument to be a plugin name, but got #{dep[1].inspect}."
- end
- if !dep[2].is_a?(String)
- self.error "Expected third dependency argument to be the plugin version, but got #{dep[2].inspect}."
- end
- dep_arg = dep[0]
- dep_name = dep[1]
- dep_version = dep[2]
- optional = false
- exact = false
- # Test for exact and optional flags
- if dep_arg == :optional || dep_arg == :exact || dep_arg == :optional_exact
- optional = dep_arg == :optional || dep_arg == :optional_exact
- exact = dep_arg == :exact || dep_arg == :optional_exact
- else
- self.error "Expected first dependency argument to be one of :optional, :exact or :optional_exact, " +
- "but got #{dep_arg.inspect}."
- end
- mustequal = exact
- # If optional
- if optional
- # Only handle if installed, but not at the required version
- if self.installed?(dep_name) && !self.installed?(dep_name, dep_version, mustequal)
- msg = "Plugin '#{name}' requires plugin '#{dep_name}', if installed, to be version #{dep_version}"
- msg << " or later " if !exact
- msg << ", but the installed version was #{self.version(dep_name)}."
- if dep_link = self.link(dep_name)
- msg << "\n\nThe plugin may be updated from: #{dep_link}"
- end
- self.error msg
- end
- # Not optional
- else
- # If installed at the right version, ignore
- if self.installed?(dep_name, dep_version, mustequal)
- # If installed, but not at the right version
- elsif self.installed?(dep_name)
- # Needs update
- msg = "Plugin '#{name}' requires plugin '#{dep_name}' to be version #{dep_version}"
- msg << " or later " if !exact
- msg << ", but the installed version was #{self.version(dep_name)}."
- if dep_link = self.link(dep_name)
- msg << "\n\nThe plugin may be updated from: #{dep_link}"
- end
- self.error msg
- # Not installed at all
- else
- msg = "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} "
- msg << "or later " if !exact
- msg << "to be installed.\nEnsure that the dependency is above this plugin in the script list."
- self.error msg
- end
- end
- end
- end
- end
- when :incompatibilities
- # Plugin incompatibilities
- incompats = value
- incompats = [incompats] if !incompats.is_a?(Array)
- for incompat in incompats
- # An incompatible plugin is installed
- if self.installed?(incompat)
- self.error "Plugin '#{name}' is incompatible with '#{incompat}'. They cannot both be used at the same time."
- end
- end
- else
- self.error "Invalid plugin registry key '#{key}'."
- end
- end
- # Add plugin to class variable
- @@Plugins[name] = {
- :name => name,
- :version => version,
- :link => link,
- :dependencies => dependencies,
- :incompatibilities => incompats
- }
- for plugin in @@Plugins.values
- if plugin[:incompatibilities] && plugin[:incompatibilities].include?(name)
- self.error "Plugin '#{plugin[:name]}' is incompatible with '#{name}'. They cannot both be used at the same time."
- end
- end
- end
- # Throws a pure error message without stack trace or any other useless info.
- def self.error(msg)
- Graphics.update
- t = Thread.new do
- MBOX.call(Win32API.pbFindRgssWindow, msg, "Plugin Error", 0x10)
- Thread.exit
- end
- while t.status
- Graphics.update
- end
- Kernel.exit! true
- end
- # Returns true if the specified plugin is installed.
- # If the version is specified, this version is taken into account.
- # If mustequal is true, the version must be a match with the specified version.
- def self.installed?(plugin_name, plugin_version = nil, mustequal = false)
- plugin = @@Plugins[plugin_name]
- return false if plugin.nil?
- return true if plugin_version.nil?
- comparison = compare_versions(plugin[:version], plugin_version)
- return true if !mustequal && comparison >= 0
- return true if mustequal && comparison == 0
- return false
- end
- # Returns the installed version of the specified plugin.
- def self.version(plugin_name)
- return if !installed?(plugin_name)
- return @@Plugins[plugin_name][:version]
- end
- # Returns the link of the specified plugin.
- def self.link(plugin_name)
- return if !installed?(plugin_name)
- return @@Plugins[plugin_name][:link]
- end
- # Internal
- # Returns whether the given string is nil, not a string or empty.
- def self.nil_or_empty(string)
- return string.nil? || !string.is_a?(String) || string.size == 0
- end
- # Internal
- # Compares two version strings with each other to see which is greater.
- def self.compare_versions(v1, v2)
- d1 = v1.split("")
- d1.insert(0, "0") if d1[0] == "."
- while d1[-1] == "."; d1 = d1[0..-2]; end
- d2 = v2.split("")
- d2.insert(0, "0") if d2[0] == "."
- while d2[-1] == "."; d2 = d2[0..-2]; end
- for i in 0...[d1.size, d2.size].max
- c1 = d1[i]
- c2 = d2[i]
- if c1
- if c2
- if c1.to_i(16) > c2.to_i(16)
- return 1
- elsif c1.to_i(16) < c2.to_i(16)
- return -1
- end
- else
- return 1
- end
- else
- if c2
- return -1
- end
- end
- end
- return 0
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement