Advertisement
M3rein

Plugin Manager

Jun 28th, 2020
1,601
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Ruby 18.39 KB | None | 0 0
  1. #==============================================================================#
  2. #                              Plugin Manager                                  #
  3. #                                by Marin                                      #
  4. #------------------------------------------------------------------------------#
  5. #   Provides a simple interface that allows plugins to require dependencies    #
  6. #   at specific versions, and to specify incompatibilities between plugins.    #
  7. #------------------------------------------------------------------------------#
  8. #                                  Usage                                       #
  9. #                                                                              #
  10. # Any Pokémon Essentials plugin should register itself using the PluginManager.#
  11. # The simplest way to do so, for a plugin without dependencies, is as follows: #
  12. # PluginManager.register({                                                     #
  13. #   :name => "Basic Plugin",                                                   #
  14. #   :version => "1.0",                                                         #
  15. #   :link => "https://reliccastle.com/link-to-the-plugin/                      #
  16. # })                                                                           #
  17. # The link portion here is optional, but recommended. This will be linked if   #
  18. # the PluginManager detects that this plugin needs to be updated.              #
  19. #                                                                              #
  20. # A plugin's version is typically in the format X.Y.Z, but the number of       #
  21. # digits does not matter. You can also use Xa, Xb, Xc, Ya, etc.                #
  22. # What matters is that you use it consistently, so that it can be compared.    #
  23. #                                                                              #
  24. #                                                                              #
  25. #                                                                              #
  26. # Now let's say we create a new plugin titled "Simple Extension", which        #
  27. # requires our previously created "Basic Plugin" to work.                      #
  28. # PluginManager.register({                                                     #
  29. #   :name => "Simple Extension",                                               #
  30. #   :version => "1.0",                                                         #
  31. #   :link => "https://reliccastle.com/link-to-the-plugin/,                     #
  32. #   :dependencies => [                                                         #
  33. #     "Basic Plugin"                                                           #
  34. #   ]                                                                          #
  35. # })                                                                           #
  36. # This will ensure that Basic Plugin is installed, ignoring the version.       #
  37. # If you have only one dependency, you can shorten it:                         #
  38. # :dependencies => "Basic Plugin"                                              #
  39. #                                                                              #
  40. #                                                                              #
  41. #                                                                              #
  42. # To enforce upwards from a specific version, you turn the dependency into an  #
  43. # array, containing the name and the version. For instance, let's say we need  #
  44. # Basic Plugin to be at least version 1.2.                                     #
  45. # PluginManager.register({                                                     #
  46. #   :name => "Simple Extension",                                               #
  47. #   :version => "1.0",                                                         #
  48. #   :link => "https://reliccastle.com/link-to-the-plugin/,                     #
  49. #   :dependencies => [                                                         #
  50. #     ["Basic Plugin", "1.2"]                                                  #
  51. #   ]                                                                          #
  52. # })                                                                           #
  53. # Now Basic Plugin is required to be version 1.2 or above.                     #
  54. #                                                                              #
  55. #                                                                              #
  56. #                                                                              #
  57. # If we want to target one specific version of a plugin, meaning no newer than #
  58. # what we specify, we have to add an :exact flag.                              #
  59. # PluginManager.register({                                                     #
  60. #   :name => "Simple Extension",                                               #
  61. #   :version => "1.0",                                                         #
  62. #   :link => "https://reliccastle.com/link-to-the-plugin/,                     #
  63. #   :dependencies => [                                                         #
  64. #     [:exact, "Basic Plugin", "1.2"                                           #
  65. #   ]                                                                          #
  66. # })                                                                           #
  67. # This requires Basic Plugin to be exactly version 1.2; no older, no newer.    #
  68. #                                                                              #
  69. #                                                                              #
  70. #                                                                              #
  71. # Let's say we create a new plugin, "QoL Improvements", but it doesn't work    #
  72. # with the Simple Extension plugin. This can happen for two pause menu plugins,#
  73. # for instance. Then QoL Improvements is incompatible with Simple Extension.   #
  74. # PluginManager.register({                                                     #
  75. #   :name => "QoL Improvements",                                               #
  76. #   :version => "1.0",                                                         #
  77. #   :link => "https://reliccastle.com/link-to-the-plugin/,                     #
  78. #   :incompatibilities => [                                                    #
  79. #     "Simple Extension"                                                       #
  80. #   ]                                                                          #
  81. # })                                                                           #
  82. # At least one of the plugins must have the incompatibilities field set for it #
  83. # to work. Now these two plugins can never be used together.                   #
  84. #                                                                              #
  85. #                                                                              #
  86. #                                                                              #
  87. # If you have a plugin that works with a different plugin, but that plugin is  #
  88. # not required for it to work, you can specify an :optional flag.              #
  89. # If a dependency has this :optional flag, it will be ignored if the dependency#
  90. # is not present, but if it is, it will test it against the specified version. #
  91. #   :name => "Other Plugin",                                                   #
  92. #   :version => "1.0",                                                         #
  93. #   :link => "https://reliccastle.com/link-to-the-plugin/,                     #
  94. #   :dependencies => [                                                         #
  95. #     [:optional, "QoL Improvements", "1.1"]                                   #
  96. #   ]                                                                          #
  97. # })                                                                           #
  98. # Now if QoL Improvements is installed, Other Plugin will require it to be     #
  99. # version 1.1 of the plugin, or later. If you need an exact match like the     #
  100. # :exact flag would provide, you would use :optional_exact.                    #
  101. #------------------------------------------------------------------------------#
  102. #                    Please give credit when using this.                       #
  103. #==============================================================================#
  104.  
  105. module PluginManager
  106.   # Win32API MessageBox function for custom errors
  107.   MBOX = Win32API.new('user32', 'MessageBox', ['I','P','P','I'], 'I')
  108.  
  109.   # Holds all registered plugin data
  110.   @@Plugins = {}
  111.  
  112.   # Registers a plugin and tests its dependencies and incompatibilities.
  113.   def self.register(options)
  114.     name = nil
  115.     version = nil
  116.     link = nil
  117.     dependencies = nil
  118.     order = [:name, :version, :link, :dependencies, :incompatibilities]
  119.     # Ensure it first reads name, as it's used in error reporting
  120.     # by sorting the keys.
  121.     keys = options.keys.sort { |a, b| order.index(a) || order.size <=> order.index(b) || order.size }
  122.     for key in keys
  123.       value = options[key]
  124.       case key
  125.       when :name
  126.         # Plugin name
  127.         if nil_or_empty(value)
  128.           self.error "Plugin name must be a non-empty string."
  129.         end
  130.         if !@@Plugins[value].nil?
  131.           self.error "Plugin by the name of '#{value}' already exists."
  132.         end
  133.         name = value
  134.       when :version
  135.         # Plugin version
  136.         if nil_or_empty(value)
  137.           self.error "Plugin version must be a string."
  138.         end
  139.         version = value
  140.       when :link
  141.         # Plugin link
  142.         if nil_or_empty(value)
  143.           self.error "Plugin link must be a string."
  144.         end
  145.         link = value
  146.       when :dependencies
  147.         # Plugin dependencies
  148.         dependencies = value
  149.         dependencies = [dependencies] if !dependencies.is_a?(Array) || !dependencies[0].is_a?(Array)
  150.         for dep in value
  151.           if dep.is_a?(String)
  152.             # Just one string
  153.             if !self.installed?(dep)
  154.               # Check if generally installed
  155.               self.error "Plugin '#{name}' requires plugin '#{dep}' to be installed.\n" +
  156.                   "Ensure that the dependency is above this plugin in the script list."
  157.             end
  158.           elsif dep.is_a?(Array)
  159.             if dep.size == 1
  160.               # Just one string, plugin name
  161.               if dep[0].is_a?(String)
  162.                 dep_name = dep[0]
  163.                 # Check if generally installed
  164.                 if !self.installed?(dep_name)
  165.                   self.error "Plugin '#{name}' requires plugin '#{dep_name}' to be installed.\n" +
  166.                       "Ensure that the dependency is above this plugin in the script list."
  167.                 end
  168.               else
  169.                 self.error "Expected the plugin name as a string, but got #{dep[0].inspect}."
  170.               end
  171.             elsif dep.size == 2
  172.               # Name and version
  173.               if dep[0].is_a?(Symbol)
  174.                 # Flags are only supported with a version specified.
  175.                 self.error "Cannot specify dependency version check modifier without specifying a version."
  176.               elsif dep[0].is_a?(String) && dep[1].is_a?(String)
  177.                 # name and version
  178.                 dep_name = dep[0]
  179.                 dep_version = dep[1]
  180.                 # If installed at specified version, ignore
  181.                 if self.installed?(dep_name, dep_version)
  182.                 # If installed, but not at the required version
  183.                 elsif self.installed?(dep_name)
  184.                   # Needs update
  185.                   msg = "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} or later, " +
  186.                       "but the installed version is #{self.version(dep_name)}."
  187.                   if dep_link = self.link(dep_name)
  188.                     msg += "\n\nThe plugin may be updated from: #{dep_link}"
  189.                   end
  190.                   self.error msg
  191.                 # Not installed at all
  192.                 else
  193.                   self.error "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} or later " +
  194.                       "to be installed.\nEnsure that the dependency is above this plugin in the script list."
  195.                 end
  196.               end
  197.             elsif dep.size == 3
  198.               # Flag, name and version
  199.               if !dep[0].is_a?(Symbol)
  200.                 self.error "Expected first dependency argument to be a symbol, but got #{dep[0].inspect}."
  201.               end
  202.               if !dep[1].is_a?(String)
  203.                 self.error "Expected second dependency argument to be a plugin name, but got #{dep[1].inspect}."
  204.               end
  205.               if !dep[2].is_a?(String)
  206.                 self.error "Expected third dependency argument to be the plugin version, but got #{dep[2].inspect}."
  207.               end
  208.               dep_arg = dep[0]
  209.               dep_name = dep[1]
  210.               dep_version = dep[2]
  211.               optional = false
  212.               exact = false
  213.               # Test for exact and optional flags
  214.               if dep_arg == :optional || dep_arg == :exact || dep_arg == :optional_exact
  215.                 optional = dep_arg == :optional || dep_arg == :optional_exact
  216.                 exact = dep_arg == :exact || dep_arg == :optional_exact
  217.               else
  218.                 self.error "Expected first dependency argument to be one of :optional, :exact or :optional_exact, "  +
  219.                     "but got #{dep_arg.inspect}."
  220.               end
  221.               mustequal = exact
  222.               # If optional
  223.               if optional
  224.                 # Only handle if installed, but not at the required version
  225.                 if self.installed?(dep_name) && !self.installed?(dep_name, dep_version, mustequal)
  226.                   msg = "Plugin '#{name}' requires plugin '#{dep_name}', if installed, to be version #{dep_version}"
  227.                   msg << " or later " if !exact
  228.                   msg << ", but the installed version was #{self.version(dep_name)}."
  229.                   if dep_link = self.link(dep_name)
  230.                     msg << "\n\nThe plugin may be updated from: #{dep_link}"
  231.                   end
  232.                   self.error msg
  233.                 end
  234.               # Not optional
  235.               else
  236.                 # If installed at the right version, ignore
  237.                 if self.installed?(dep_name, dep_version, mustequal)
  238.                 # If installed, but not at the right version
  239.                 elsif self.installed?(dep_name)
  240.                   # Needs update
  241.                   msg = "Plugin '#{name}' requires plugin '#{dep_name}' to be version #{dep_version}"
  242.                   msg << " or later " if !exact
  243.                   msg << ", but the installed version was #{self.version(dep_name)}."
  244.                   if dep_link = self.link(dep_name)
  245.                     msg << "\n\nThe plugin may be updated from: #{dep_link}"
  246.                   end
  247.                   self.error msg
  248.                 # Not installed at all
  249.                 else
  250.                   msg = "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} "
  251.                   msg << "or later " if !exact
  252.                   msg << "to be installed.\nEnsure that the dependency is above this plugin in the script list."
  253.                   self.error msg
  254.                 end
  255.               end
  256.             end
  257.           end
  258.         end
  259.       when :incompatibilities
  260.         # Plugin incompatibilities
  261.         incompats = value
  262.         incompats = [incompats] if !incompats.is_a?(Array)
  263.         for incompat in incompats
  264.           # An incompatible plugin is installed
  265.           if self.installed?(incompat)
  266.             self.error "Plugin '#{name}' is incompatible with '#{incompat}'. They cannot both be used at the same time."
  267.           end
  268.         end
  269.       else
  270.         self.error "Invalid plugin registry key '#{key}'."
  271.       end
  272.     end
  273.     # Add plugin to class variable
  274.     @@Plugins[name] = {
  275.       :name => name,
  276.       :version => version,
  277.       :link => link,
  278.       :dependencies => dependencies,
  279.       :incompatibilities => incompats
  280.     }
  281.     for plugin in @@Plugins.values
  282.       if plugin[:incompatibilities] && plugin[:incompatibilities].include?(name)
  283.         self.error "Plugin '#{plugin[:name]}' is incompatible with '#{name}'. They cannot both be used at the same time."
  284.       end
  285.     end
  286.   end
  287.  
  288.   # Throws a pure error message without stack trace or any other useless info.
  289.   def self.error(msg)
  290.     Graphics.update
  291.     t = Thread.new do
  292.       MBOX.call(Win32API.pbFindRgssWindow, msg, "Plugin Error", 0x10)
  293.       Thread.exit
  294.     end
  295.     while t.status
  296.       Graphics.update
  297.     end
  298.     Kernel.exit! true
  299.   end
  300.  
  301.   # Returns true if the specified plugin is installed.
  302.   # If the version is specified, this version is taken into account.
  303.   # If mustequal is true, the version must be a match with the specified version.
  304.   def self.installed?(plugin_name, plugin_version = nil, mustequal = false)
  305.     plugin = @@Plugins[plugin_name]
  306.     return false if plugin.nil?
  307.     return true if plugin_version.nil?
  308.     comparison = compare_versions(plugin[:version], plugin_version)
  309.     return true if !mustequal && comparison >= 0
  310.     return true if mustequal && comparison == 0
  311.     return false
  312.   end
  313.  
  314.   # Returns the installed version of the specified plugin.
  315.   def self.version(plugin_name)
  316.     return if !installed?(plugin_name)
  317.     return @@Plugins[plugin_name][:version]
  318.   end
  319.  
  320.   # Returns the link of the specified plugin.
  321.   def self.link(plugin_name)
  322.     return if !installed?(plugin_name)
  323.     return @@Plugins[plugin_name][:link]
  324.   end
  325.  
  326.   # Internal
  327.   # Returns whether the given string is nil, not a string or empty.
  328.   def self.nil_or_empty(string)
  329.     return string.nil? || !string.is_a?(String) || string.size == 0
  330.   end
  331.  
  332.    # Internal
  333.   # Compares two version strings with each other to see which is greater.
  334.   def self.compare_versions(v1, v2)
  335.     d1 = v1.split("")
  336.     d1.insert(0, "0") if d1[0] == "."
  337.     while d1[-1] == "."; d1 = d1[0..-2]; end
  338.     d2 = v2.split("")
  339.     d2.insert(0, "0") if d2[0] == "."
  340.     while d2[-1] == "."; d2 = d2[0..-2]; end
  341.     for i in 0...[d1.size, d2.size].max
  342.       c1 = d1[i]
  343.       c2 = d2[i]
  344.       if c1
  345.         if c2
  346.           if c1.to_i(16) > c2.to_i(16)
  347.             return 1
  348.           elsif c1.to_i(16) < c2.to_i(16)
  349.             return -1
  350.           end
  351.         else
  352.           return 1
  353.         end
  354.       else
  355.         if c2
  356.           return -1
  357.         end
  358.       end
  359.     end
  360.     return 0
  361.   end
  362. end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement