vop 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +66 -0
- data/README.md +2 -0
- data/Rakefile +6 -0
- data/bin/vop.rb +28 -0
- data/bin/vop.sh +4 -0
- data/exe/vop +28 -0
- data/lib/vop.rb +242 -0
- data/lib/vop/command.rb +168 -0
- data/lib/vop/command_loader.rb +47 -0
- data/lib/vop/entity.rb +61 -0
- data/lib/vop/loader.rb +35 -0
- data/lib/vop/plugin.rb +141 -0
- data/lib/vop/plugin_loader.rb +88 -0
- data/lib/vop/plugins/core/commands/clear_context.rb +3 -0
- data/lib/vop/plugins/core/commands/collect_contributions.rb +31 -0
- data/lib/vop/plugins/core/commands/edit.rb +12 -0
- data/lib/vop/plugins/core/commands/help.rb +38 -0
- data/lib/vop/plugins/core/commands/identity.rb +4 -0
- data/lib/vop/plugins/core/commands/list_contributors.rb +8 -0
- data/lib/vop/plugins/core/commands/list_entities.rb +3 -0
- data/lib/vop/plugins/core/commands/pry.rb +9 -0
- data/lib/vop/plugins/core/commands/reset.rb +5 -0
- data/lib/vop/plugins/core/commands/show_context.rb +3 -0
- data/lib/vop/plugins/core/commands/source.rb +5 -0
- data/lib/vop/plugins/core/commands/system_call.rb +5 -0
- data/lib/vop/plugins/core/core.plugin +4 -0
- data/lib/vop/plugins/core/helpers/command_loader/command_syntax.rb +45 -0
- data/lib/vop/plugins/core/helpers/command_loader/contributions.rb +28 -0
- data/lib/vop/plugins/core/helpers/command_loader/entities.rb +57 -0
- data/lib/vop/plugins/core/helpers/helper.rb +3 -0
- data/lib/vop/plugins/core/helpers/plugin_loader/plugin_syntax.rb +0 -0
- data/lib/vop/plugins/meta/commands/add_search_path.rb +6 -0
- data/lib/vop/plugins/meta/commands/delete_plugin.rb +13 -0
- data/lib/vop/plugins/meta/commands/list_commands.rb +17 -0
- data/lib/vop/plugins/meta/commands/list_plugins.rb +8 -0
- data/lib/vop/plugins/meta/commands/new_command.rb +14 -0
- data/lib/vop/plugins/meta/commands/new_plugin.rb +25 -0
- data/lib/vop/plugins/meta/commands/show_search_path.rb +3 -0
- data/lib/vop/plugins/meta/commands/who_provides.rb +5 -0
- data/lib/vop/plugins/meta/meta.plugin +1 -0
- data/lib/vop/plugins/ssh/commands/scp.rb +11 -0
- data/lib/vop/plugins/ssh/commands/ssh.rb +19 -0
- data/lib/vop/plugins/ssh/ssh.plugin +1 -0
- data/lib/vop/shell.rb +52 -0
- data/lib/vop/shell/backend.rb +28 -0
- data/lib/vop/shell/base_shell.rb +112 -0
- data/lib/vop/shell/formatter.rb +46 -0
- data/lib/vop/shell/vop_shell_backend.rb +257 -0
- data/lib/vop/version.rb +3 -0
- data/vop.gemspec +31 -0
- metadata +223 -0
data/lib/vop/command.rb
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
module Vop
|
2
|
+
|
3
|
+
class Command
|
4
|
+
|
5
|
+
attr_reader :name
|
6
|
+
attr_reader :plugin
|
7
|
+
|
8
|
+
attr_accessor :block
|
9
|
+
attr_accessor :description
|
10
|
+
attr_accessor :show_options
|
11
|
+
|
12
|
+
attr_reader :params
|
13
|
+
|
14
|
+
def initialize(plugin, name)
|
15
|
+
@plugin = plugin
|
16
|
+
@name = name
|
17
|
+
@block = lambda { |params| $logger.warn "#{name} not yet implemented!" }
|
18
|
+
@params = []
|
19
|
+
@show_options = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"Vop::Command #{@name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def source
|
27
|
+
@plugin.command_source(name)
|
28
|
+
end
|
29
|
+
|
30
|
+
def short_name
|
31
|
+
name.split(".").last
|
32
|
+
end
|
33
|
+
|
34
|
+
def param(name)
|
35
|
+
params.select { |param| param[:name] == name }.first || nil
|
36
|
+
end
|
37
|
+
|
38
|
+
# the default param is the one used when a command is called with a single "scalar" param only, like
|
39
|
+
# @op.foo("zaphod")
|
40
|
+
# if a parameter is marked as default, it will be assigned the value "zaphod" in this case.
|
41
|
+
# if there's only a single param, it's the default param by default (ha!)
|
42
|
+
def default_param
|
43
|
+
if params.size == 1
|
44
|
+
params.first
|
45
|
+
else
|
46
|
+
params.select { |param| param[:default_param] == true }.first || nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def mandatory_params
|
51
|
+
params.select do |p|
|
52
|
+
p[:mandatory]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def lookup(param_name, params)
|
57
|
+
p = param(param_name)
|
58
|
+
raise "no such param : #{param_name} in command #{name}" unless p
|
59
|
+
|
60
|
+
if p.has_key? :lookup
|
61
|
+
p[:lookup].call(params)
|
62
|
+
else
|
63
|
+
$logger.debug "no lookups for #{param_name}"
|
64
|
+
[]
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# accepts arguments as handed in by :define_method and prepares them
|
69
|
+
# into the +params+ structure expected by command blocks
|
70
|
+
def prepare_params(ruby_args, extra = {})
|
71
|
+
result = {}
|
72
|
+
if ruby_args
|
73
|
+
if ruby_args.is_a? Hash
|
74
|
+
result = ruby_args
|
75
|
+
ruby_args.each do |k,v|
|
76
|
+
p = param(k)
|
77
|
+
if p
|
78
|
+
# values are auto-boxed into an array if the param expects multiple values
|
79
|
+
if p[:multi] && ! v.is_a?(Array) then
|
80
|
+
$logger.debug("autoboxing for #{p[:name]}")
|
81
|
+
v = [ v ]
|
82
|
+
# array values are auto-unboxed if the param doesn't want multi
|
83
|
+
elsif ! p[:multi] && v.is_a?(Array) && v.length == 1
|
84
|
+
$logger.debug("autounboxing for #{p[:name]}")
|
85
|
+
v = v.first
|
86
|
+
end
|
87
|
+
end
|
88
|
+
result[k] = v
|
89
|
+
end
|
90
|
+
else
|
91
|
+
# if there's a default param, it can be passed as "scalar" param
|
92
|
+
# (as opposed to the usual hash) to execute, but will be converted
|
93
|
+
# into a "normal" named param
|
94
|
+
dp = default_param
|
95
|
+
if dp
|
96
|
+
result = {dp[:name] => ruby_args}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
if extra.keys.size > 0
|
102
|
+
result.merge! extra
|
103
|
+
end
|
104
|
+
|
105
|
+
# add in defaults (for all params that have not been specified)
|
106
|
+
params.each do |p|
|
107
|
+
unless result.has_key? p[:name]
|
108
|
+
if p[:default]
|
109
|
+
result[p[:name]] = p[:default]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
117
|
+
def execute(param_values, extra = {})
|
118
|
+
prepared = prepare_params(param_values, extra)
|
119
|
+
|
120
|
+
block_param_names = self.block.parameters.map { |x| x.last }
|
121
|
+
|
122
|
+
#puts "executing #{self.name} : prepared : #{prepared.inspect}"
|
123
|
+
|
124
|
+
payload = []
|
125
|
+
context = {} # TODO should this be initialized?
|
126
|
+
|
127
|
+
block_param_names.each do |name|
|
128
|
+
param = nil
|
129
|
+
|
130
|
+
case name.to_s
|
131
|
+
when 'params'
|
132
|
+
param = prepared
|
133
|
+
when 'plugin'
|
134
|
+
param = self.plugin
|
135
|
+
when 'context'
|
136
|
+
param = context
|
137
|
+
when 'shell'
|
138
|
+
raise "shell not supported" unless extra.has_key? 'shell'
|
139
|
+
param = extra['shell']
|
140
|
+
else
|
141
|
+
if prepared.has_key? name.to_s
|
142
|
+
param = prepared[name.to_s]
|
143
|
+
elsif prepared.has_key? name
|
144
|
+
param = prepared[name]
|
145
|
+
else
|
146
|
+
raise "unknown block param name : >>#{name}<<"
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# from the black magick department: block parameters with the
|
151
|
+
# same name as an entity get auto-inflated
|
152
|
+
if param
|
153
|
+
entity_names = @plugin.op.core.state[:entities].map { |entity| entity[:name] }
|
154
|
+
if entity_names.include? name.to_s
|
155
|
+
resolved = @plugin.op.send(name, param)
|
156
|
+
param = resolved
|
157
|
+
end
|
158
|
+
payload << param
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
result = self.block.call(*payload)
|
163
|
+
[result, context]
|
164
|
+
end
|
165
|
+
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'vop/command'
|
2
|
+
|
3
|
+
module Vop
|
4
|
+
|
5
|
+
class CommandLoader
|
6
|
+
|
7
|
+
def initialize(plugin)
|
8
|
+
@plugin = plugin
|
9
|
+
@op = plugin.op
|
10
|
+
|
11
|
+
@commands = {}
|
12
|
+
|
13
|
+
# we need to load both the general helpers of the plugin because they are
|
14
|
+
# used inside commands as well as the helpers specific to the command_loader
|
15
|
+
@plugin.inject_helpers(self)
|
16
|
+
@plugin.inject_helpers(self, 'command_loader')
|
17
|
+
end
|
18
|
+
|
19
|
+
def new_command(name)
|
20
|
+
@command = Command.new(@plugin, name)
|
21
|
+
@commands[@command.name] = @command
|
22
|
+
@command
|
23
|
+
end
|
24
|
+
|
25
|
+
def run(&block)
|
26
|
+
@command.block = block
|
27
|
+
end
|
28
|
+
|
29
|
+
def read_sources(named_commands)
|
30
|
+
# reads a hash of <command_name> => <source string>
|
31
|
+
named_commands.each do |name, source|
|
32
|
+
|
33
|
+
new_command(name)
|
34
|
+
|
35
|
+
begin
|
36
|
+
self.instance_eval(source[:code], source[:file_name])
|
37
|
+
rescue => detail
|
38
|
+
raise "problem loading plugin #{name} : #{detail.message}\n#{detail.backtrace.join("\n")}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
@commands
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
data/lib/vop/entity.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
module Vop
|
2
|
+
|
3
|
+
# An entity is something that is identifiable through a key, e.g. a name.
|
4
|
+
# Also, it groups commands that accept the same parameter: When a command
|
5
|
+
# is called on an entity, the parameter with the same name is filled, so
|
6
|
+
# @op.machine("localhost").processes()
|
7
|
+
# is equivalent to
|
8
|
+
# @op.processes(machine: "localhost")
|
9
|
+
class Entity
|
10
|
+
|
11
|
+
def initialize(op, name, key, hash)
|
12
|
+
@op = op
|
13
|
+
@name = name
|
14
|
+
@key = key
|
15
|
+
@data = {}
|
16
|
+
hash.each do |k,v|
|
17
|
+
@data[k.to_s] = v
|
18
|
+
end
|
19
|
+
|
20
|
+
make_methods_for_hash_keys
|
21
|
+
make_methods_for_commands
|
22
|
+
end
|
23
|
+
|
24
|
+
def inspect
|
25
|
+
"Vop::Entity #{@name} (#{@command_count} commands)"
|
26
|
+
end
|
27
|
+
|
28
|
+
def make_methods_for_hash_keys
|
29
|
+
@data.keys.each do |key|
|
30
|
+
#next if ['name', 'key'].include? key.to_s
|
31
|
+
next if ['key'].include? key.to_s
|
32
|
+
self.class.send(:define_method, key) do |*args|
|
33
|
+
ruby_args = args.length > 0 ? args[0] : {}
|
34
|
+
@data[key]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def entity_commands
|
40
|
+
result = @op.commands.values.select do |command|
|
41
|
+
command.params.select do |param|
|
42
|
+
param[:name] == @name
|
43
|
+
end.count > 0
|
44
|
+
end
|
45
|
+
@command_count = result.count
|
46
|
+
result
|
47
|
+
end
|
48
|
+
|
49
|
+
def make_methods_for_commands
|
50
|
+
entity_commands.each do |command|
|
51
|
+
value = @data[@key]
|
52
|
+
self.class.send(:define_method, command.short_name) do |*args|
|
53
|
+
ruby_args = args.length > 0 ? args[0] : {}
|
54
|
+
@op.execute(command.short_name, ruby_args, { @name => value })
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
data/lib/vop/loader.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
class Loader
|
2
|
+
|
3
|
+
attr_reader :loaded
|
4
|
+
|
5
|
+
def initialize(op, plugin)
|
6
|
+
@op = op
|
7
|
+
@plugin = plugin
|
8
|
+
@loaded = []
|
9
|
+
|
10
|
+
# TODO plugin.load_helper_classes(self) unless plugin == nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def from_scratch(name)
|
14
|
+
fresh = { "name" => name }
|
15
|
+
@loaded << fresh
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.read(op, plugin, name, source, file_name = nil)
|
19
|
+
loader = new(op, plugin)
|
20
|
+
|
21
|
+
loader.from_scratch(name)
|
22
|
+
begin
|
23
|
+
if file_name
|
24
|
+
loader.instance_eval source, file_name
|
25
|
+
else
|
26
|
+
loader.instance_eval source
|
27
|
+
end
|
28
|
+
rescue => detail
|
29
|
+
raise "could not read '#{name}' : #{detail.message}\n#{detail.backtrace[0..9].join("\n")}"
|
30
|
+
end
|
31
|
+
|
32
|
+
loader
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
data/lib/vop/plugin.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'vop/command_loader'
|
2
|
+
|
3
|
+
module Vop
|
4
|
+
|
5
|
+
class Plugin
|
6
|
+
|
7
|
+
attr_reader :op
|
8
|
+
attr_reader :name
|
9
|
+
attr_reader :path
|
10
|
+
|
11
|
+
attr_reader :commands
|
12
|
+
attr_reader :config
|
13
|
+
attr_reader :dependencies
|
14
|
+
|
15
|
+
attr_reader :state
|
16
|
+
|
17
|
+
def initialize(op, plugin_name, plugin_path)
|
18
|
+
@op = op
|
19
|
+
@name = plugin_name
|
20
|
+
@path = plugin_path
|
21
|
+
@config = {}
|
22
|
+
@dependencies = []
|
23
|
+
|
24
|
+
# all plugins depend on 'core' (unless they are core or some murky dummy)
|
25
|
+
independents = %w|core __root__|
|
26
|
+
@dependencies << 'core' unless independents.include? plugin_name
|
27
|
+
|
28
|
+
@state = {}
|
29
|
+
@hooks = {}
|
30
|
+
|
31
|
+
@sources = Hash.new { |h, k| h[k] = {} }
|
32
|
+
end
|
33
|
+
|
34
|
+
def inspect
|
35
|
+
"Vop::Plugin #{@name}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def hook(name, &block)
|
39
|
+
@hooks[name.to_sym] = block
|
40
|
+
end
|
41
|
+
|
42
|
+
def call_hook(name, *args)
|
43
|
+
if @hooks.has_key? name
|
44
|
+
$logger.debug "plugin #{self.name} calling hook #{name}"
|
45
|
+
@hooks[name].call(self, *args)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def init
|
50
|
+
$logger.debug "plugin init #{@name}"
|
51
|
+
call_hook :preload
|
52
|
+
load_helpers
|
53
|
+
load_config
|
54
|
+
# TODO we might want to activate/register only plugins with enough config
|
55
|
+
call_hook :init
|
56
|
+
load_commands
|
57
|
+
call_hook :activate
|
58
|
+
end
|
59
|
+
|
60
|
+
def plugin_dir(name)
|
61
|
+
@path + '/' + name.to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
def load_code_from_dir(type_name)
|
65
|
+
dir = plugin_dir type_name
|
66
|
+
if File.exists?(dir)
|
67
|
+
Dir.glob(File.join(dir, '*.rb')).each do |file_name|
|
68
|
+
name_from_file = /#{dir}\/(.+).rb$/.match(file_name).captures.first
|
69
|
+
full_name = @name + '.' + name_from_file
|
70
|
+
$logger.debug(" #{type_name} << #{full_name}")
|
71
|
+
|
72
|
+
code = File.read(file_name)
|
73
|
+
@sources[type_name][full_name] = {
|
74
|
+
:file_name => file_name,
|
75
|
+
:code => code
|
76
|
+
}
|
77
|
+
end
|
78
|
+
else
|
79
|
+
#$logger.debug "no #{type_name} dir found - checked for #{dir}"
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def load_helpers
|
84
|
+
load_code_from_dir 'helpers'
|
85
|
+
load_code_from_dir 'helpers/command_loader'
|
86
|
+
load_code_from_dir 'helpers/plugin_loader'
|
87
|
+
end
|
88
|
+
|
89
|
+
def helper_sources(type_name = 'helpers')
|
90
|
+
@sources[type_name].map do |name, source|
|
91
|
+
source[:code]
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def command_source(command_name)
|
96
|
+
@sources[:commands][command_name]
|
97
|
+
end
|
98
|
+
|
99
|
+
def inject_helpers(target, sub_type_name = nil)
|
100
|
+
type_name = 'helpers'
|
101
|
+
if sub_type_name
|
102
|
+
type_name += '/' + sub_type_name
|
103
|
+
end
|
104
|
+
|
105
|
+
plugins_to_load_helpers_from = [ self ]
|
106
|
+
|
107
|
+
self.dependencies.each do |name|
|
108
|
+
other = @op.plugins[name]
|
109
|
+
raise "can not resolve plugin dependency #{name}" unless other
|
110
|
+
plugins_to_load_helpers_from << other
|
111
|
+
end
|
112
|
+
|
113
|
+
plugins_to_load_helpers_from.each do |other_plugin|
|
114
|
+
next if other_plugin.helper_sources(type_name).size == 0
|
115
|
+
#$logger.debug "loading helper from #{other_plugin.name} into #{target} : #{other_plugin.helper_sources.size}"
|
116
|
+
|
117
|
+
helper_module = Module.new()
|
118
|
+
other_plugin.helper_sources(type_name).each do |source|
|
119
|
+
helper_module.class_eval source
|
120
|
+
end
|
121
|
+
target.extend helper_module
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
def load_commands
|
126
|
+
load_code_from_dir :commands
|
127
|
+
|
128
|
+
loader = CommandLoader.new(self)
|
129
|
+
@commands = loader.read_sources @sources[:commands]
|
130
|
+
@commands.each do |name, command|
|
131
|
+
# TODO might want to warn/debug about overrides here
|
132
|
+
@op.eat(command)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def load_config
|
137
|
+
end
|
138
|
+
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|