1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
|
# frozen_string_literal: true
module Bundler
# Manages which plugins are installed and their sources. This also is supposed to map
# which plugin does what (currently the features are not implemented so this class is
# now a stub class).
module Plugin
class Index
class CommandConflict < PluginError
def initialize(plugin, commands)
msg = "Command(s) `#{commands.join("`, `")}` declared by #{plugin} are already registered."
super msg
end
end
class SourceConflict < PluginError
def initialize(plugin, sources)
msg = "Source(s) `#{sources.join("`, `")}` declared by #{plugin} are already registered."
super msg
end
end
attr_reader :commands
def initialize
@plugin_paths = {}
@commands = {}
@sources = {}
@hooks = {}
@load_paths = {}
begin
load_index(global_index_file, true)
rescue GenericSystemCallError
# no need to fail when on a read-only FS, for example
nil
end
load_index(local_index_file) if SharedHelpers.in_bundle?
end
# This function is to be called when a new plugin is installed. This
# function shall add the functions of the plugin to existing maps and also
# the name to source location.
#
# @param [String] name of the plugin to be registered
# @param [String] path where the plugin is installed
# @param [Array<String>] load_paths for the plugin
# @param [Array<String>] commands that are handled by the plugin
# @param [Array<String>] sources that are handled by the plugin
def register_plugin(name, path, load_paths, commands, sources, hooks)
old_commands = @commands.dup
common = commands & @commands.keys
raise CommandConflict.new(name, common) unless common.empty?
commands.each {|c| @commands[c] = name }
common = sources & @sources.keys
raise SourceConflict.new(name, common) unless common.empty?
sources.each {|k| @sources[k] = name }
hooks.each do |event|
event_hooks = (@hooks[event] ||= []) << name
event_hooks.uniq!
end
@plugin_paths[name] = path
@load_paths[name] = load_paths
save_index
rescue StandardError
@commands = old_commands
raise
end
def unregister_plugin(name)
@commands.delete_if {|_, v| v == name }
@sources.delete_if {|_, v| v == name }
@hooks.each do |hook, names|
names.delete(name)
@hooks.delete(hook) if names.empty?
end
@plugin_paths.delete(name)
@load_paths.delete(name)
save_index
end
# Path of default index file
def index_file
Plugin.root.join("index")
end
# Path where the global index file is stored
def global_index_file
Plugin.global_root.join("index")
end
# Path where the local index file is stored
def local_index_file
Plugin.local_root.join("index")
end
def plugin_path(name)
Pathname.new @plugin_paths[name]
end
def load_paths(name)
@load_paths[name]
end
# Fetch the name of plugin handling the command
def command_plugin(command)
@commands[command]
end
def installed?(name)
@plugin_paths[name]
end
def installed_plugins
@plugin_paths.keys
end
def plugin_commands(plugin)
@commands.find_all {|_, n| n == plugin }.map(&:first)
end
def source?(source)
@sources.key? source
end
def source_plugin(name)
@sources[name]
end
# Returns the list of plugin names handling the passed event
def hook_plugins(event)
@hooks[event] || []
end
private
# Reads the index file from the directory and initializes the instance
# variables.
#
# It skips the sources if the second param is true
# @param [Pathname] index file path
# @param [Boolean] is the index file global index
def load_index(index_file, global = false)
SharedHelpers.filesystem_access(index_file, :read) do |index_f|
valid_file = index_f && index_f.exist? && !index_f.size.zero?
break unless valid_file
data = index_f.read
require_relative "../yaml_serializer"
index = YAMLSerializer.load(data)
@commands.merge!(index["commands"])
@hooks.merge!(index["hooks"])
@load_paths.merge!(index["load_paths"])
@plugin_paths.merge!(index["plugin_paths"])
@sources.merge!(index["sources"]) unless global
end
end
# Should be called when any of the instance variables change. Stores the
# instance variables in YAML format. (The instance variables are supposed
# to be only String key value pairs)
def save_index
index = {
"commands" => @commands,
"hooks" => @hooks,
"load_paths" => @load_paths,
"plugin_paths" => @plugin_paths,
"sources" => @sources,
}
require_relative "../yaml_serializer"
SharedHelpers.filesystem_access(index_file) do |index_f|
FileUtils.mkdir_p(index_f.dirname)
File.open(index_f, "w") {|f| f.puts YAMLSerializer.dump(index) }
end
end
end
end
end
|