Module: Chronicle::ETL::Registry::Plugins

Defined in:
lib/chronicle/etl/registry/plugins.rb

Overview

TODO:

Better validation for whether a gem is actually a plugin

TODO:

Add ways to load a plugin that don't require a gem on rubygems.org

Responsible for managing plugins available to chronicle-etl

Constant Summary collapse

KNOWN_PLUGINS =
%w[
  apple-podcasts
  email
  foursquare
  github
  imessage
  pinboard
  safari
  shell
  spotify
  zulip
].freeze

Class Method Summary collapse

Class Method Details

.activate(name) ⇒ Object

Activate a plugin with given name by requireing it



125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
# File 'lib/chronicle/etl/registry/plugins.rb', line 125

def self.activate(name)
  # By default, activates the latest available version of a gem
  # so don't have to run Kernel#gem separately

  plugin_require_name = name.to_s.gsub('-', '_')
  require "chronicle/#{plugin_require_name}"
rescue Gem::ConflictError => e
  # TODO: figure out if there's more we can do here
  raise Chronicle::ETL::PluginConflictError.new(name),
    "Plugin '#{plugin_require_name}' couldn't be loaded. #{e.message}"
rescue StandardError, LoadError
  # StandardError to catch random non-loading problems that might occur
  # when requiring the plugin (eg class macro invoked the wrong way)
  # TODO: decide if this should be separated
  raise Chronicle::ETL::PluginLoadError.new(name), "Plugin '#{plugin_require_name}' couldn't be loaded"
end

.allObject

Union of installed gems (latest version) + available gems



97
98
99
100
101
102
# File 'lib/chronicle/etl/registry/plugins.rb', line 97

def self.all
  (installed + available)
    .group_by(&:name)
    .transform_values { |plugin| plugin.find(&:installed) || plugin.first }
    .values
end

.availableObject

List of all plugins available to chronicle-etl



72
73
74
# File 'lib/chronicle/etl/registry/plugins.rb', line 72

def self.available
  available_as_gem
end

.available_as_gemObject

List of plugins available through rubygems TODO: make this concurrent



78
79
80
81
82
83
84
85
86
87
88
# File 'lib/chronicle/etl/registry/plugins.rb', line 78

def self.available_as_gem
  KNOWN_PLUGINS.map do |name|
    info = gem_info(name)
    Chronicle::ETL::Registry::PluginRegistration.new do |p|
      p.name = name
      p.gem = info['name']
      p.version = info['version']
      p.description = info['info']
    end
  end
end

.exists?(name) ⇒ Boolean

Does a plugin with a given name exist?

Returns:

  • (Boolean)


105
106
107
# File 'lib/chronicle/etl/registry/plugins.rb', line 105

def self.exists?(name)
  KNOWN_PLUGINS.include?(name)
end

.gem_info(name) ⇒ Object

Load info about a gem plugin from rubygems API



91
92
93
94
# File 'lib/chronicle/etl/registry/plugins.rb', line 91

def self.gem_info(name)
  gem_name = "chronicle-#{name}"
  Gems.info(gem_name)
end

.install(name) ⇒ Object

Install a plugin to local gems



143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/chronicle/etl/registry/plugins.rb', line 143

def self.install(name)
  return if installed?(name)
  raise(Chronicle::ETL::PluginNotAvailableError.new(name), "Plugin #{name} doesn't exist") unless exists?(name)

  gem_name = "chronicle-#{name}"

  Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
  Gem.install(gem_name)

  activate(name)
rescue Gem::UnsatisfiableDependencyError
  # TODO: we need to catch a lot more than this here
  raise Chronicle::ETL::PluginNotAvailableError.new(name), "Plugin #{name} could not be installed."
end

.installedObject

Plugins either installed as gems or manually loaded/registered



44
45
46
# File 'lib/chronicle/etl/registry/plugins.rb', line 44

def self.installed
  installed_standalone + installed_as_gem
end

.installed?(name) ⇒ Boolean

Check whether a given plugin is installed

Returns:

  • (Boolean)


49
50
51
# File 'lib/chronicle/etl/registry/plugins.rb', line 49

def self.installed?(name)
  installed.map(&:name).include?(name.to_sym)
end

.installed_as_gemObject

List of plugins installed as gems



59
60
61
62
63
64
65
66
67
68
69
# File 'lib/chronicle/etl/registry/plugins.rb', line 59

def self.installed_as_gem
  installed_gemspecs_latest.map do |gem|
    Chronicle::ETL::Registry::PluginRegistration.new do |p|
      p.name = gem.name.sub('chronicle-', '').to_sym
      p.gem = gem.name
      p.description = gem.description
      p.version = gem.version.to_s
      p.installed = true
    end
  end
end

.installed_gemspecsObject

All versions of all plugins currently installed



110
111
112
113
114
115
# File 'lib/chronicle/etl/registry/plugins.rb', line 110

def self.installed_gemspecs
  # TODO: add check for chronicle-etl dependency
  Gem::Specification.filter do |s|
    s.name.match(/^chronicle-/) && s.name != 'chronicle-etl' && s.name != 'chronicle-core'
  end
end

.installed_gemspecs_latestObject

Latest version of each installed plugin



118
119
120
121
122
# File 'lib/chronicle/etl/registry/plugins.rb', line 118

def self.installed_gemspecs_latest
  installed_gemspecs.group_by(&:name)
    .transform_values { |versions| versions.sort_by(&:version).reverse.first }
    .values
end

.installed_standaloneObject

List of plugins installed as standalone



54
55
56
# File 'lib/chronicle/etl/registry/plugins.rb', line 54

def self.installed_standalone
  @installed_standalone ||= []
end

.register_standalone(name:) ⇒ Object

Start of a system for having non-gem plugins. Right now, we just make registry aware of existence of name of non-gem plugin



34
35
36
37
38
39
40
41
# File 'lib/chronicle/etl/registry/plugins.rb', line 34

def self.register_standalone(name:)
  plugin = Chronicle::ETL::Registry::PluginRegistration.new do |p|
    p.name = name.to_sym
    p.installed = true
  end

  installed_standalone << plugin
end

.uninstall(name) ⇒ Object

Uninstall a plugin



159
160
161
162
163
164
165
166
167
# File 'lib/chronicle/etl/registry/plugins.rb', line 159

def self.uninstall(name)
  gem_name = "chronicle-#{name}"
  Gem::DefaultUserInteraction.ui = Gem::SilentUI.new
  uninstaller = Gem::Uninstaller.new(gem_name)
  uninstaller.uninstall
rescue Gem::InstallError
  # TODO: strengthen this exception handling
  raise(Chronicle::ETL::PluginError.new(name), "Plugin #{name} wasn't uninstalled")
end