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
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'vop/plugin'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
module Vop
|
5
|
+
|
6
|
+
class PluginLoader
|
7
|
+
|
8
|
+
attr_reader :op
|
9
|
+
attr_reader :dir
|
10
|
+
|
11
|
+
def initialize(op, dir)
|
12
|
+
@op = op
|
13
|
+
@dir = dir
|
14
|
+
end
|
15
|
+
|
16
|
+
def new_plugin(plugin_name, plugin_path)
|
17
|
+
@plugin = Plugin.new(@op, plugin_name, plugin_path)
|
18
|
+
|
19
|
+
# TODO activate plugin_loader helpers?
|
20
|
+
# (this will allow plugins to define helpers that can be used in plugins)
|
21
|
+
#@plugin.inject_helpers(self)
|
22
|
+
#@plugin.inject_helpers(self, 'plugin_loader')
|
23
|
+
|
24
|
+
@op.plugins[plugin_name] = @plugin
|
25
|
+
@plugin
|
26
|
+
end
|
27
|
+
|
28
|
+
def on(hook_sym, &block)
|
29
|
+
@plugin.hook(hook_sym, &block)
|
30
|
+
|
31
|
+
core_hooks = %i|before_execute after_execute|
|
32
|
+
if core_hooks.include? hook_sym
|
33
|
+
@op.hook(hook_sym, @plugin.name)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def dependency(sym)
|
38
|
+
@plugin.dependencies << sym.to_s
|
39
|
+
end
|
40
|
+
alias_method :depends, :dependency
|
41
|
+
|
42
|
+
def read_plugin_template
|
43
|
+
template_path = File.join(@dir, 'plugin.vop')
|
44
|
+
plugin_template = nil
|
45
|
+
if File.exists?(template_path)
|
46
|
+
plugin_template = IO.read(template_path)
|
47
|
+
end
|
48
|
+
if plugin_template
|
49
|
+
begin
|
50
|
+
self.instance_eval plugin_template
|
51
|
+
rescue => e
|
52
|
+
$stderr.puts "could not load plugin #{template_path} : " + e.message
|
53
|
+
$stderr.puts e.backtrace.join("\n")
|
54
|
+
raise e
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.read(op, dir)
|
60
|
+
$logger.debug("scanning dir #{dir} for plugins...")
|
61
|
+
|
62
|
+
loader = new(op, dir)
|
63
|
+
loader.read_plugin_template
|
64
|
+
|
65
|
+
Dir.new(dir).each do |plugin_name|
|
66
|
+
next if /^\./.match(plugin_name)
|
67
|
+
|
68
|
+
plugin_path = File.join(dir, plugin_name)
|
69
|
+
file_name = File.join(plugin_path, "#{plugin_name}.plugin")
|
70
|
+
next unless File.exists?(file_name)
|
71
|
+
$logger.debug("reading plugin '#{plugin_name}' from '#{file_name}'")
|
72
|
+
|
73
|
+
plugin = loader.new_plugin(plugin_name, plugin_path)
|
74
|
+
|
75
|
+
code = File.read(file_name)
|
76
|
+
begin
|
77
|
+
loader.instance_eval(code, file_name)
|
78
|
+
rescue => detail
|
79
|
+
raise "problem loading plugin #{plugin_name} : #{detail.message}\n#{detail.backtrace.join("\n")}"
|
80
|
+
end
|
81
|
+
|
82
|
+
$logger.debug "loaded plugin #{plugin_name}"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
param! "name"
|
2
|
+
param "except", :multi => true
|
3
|
+
param! "raw_params"
|
4
|
+
|
5
|
+
run do |name, raw_params, params|
|
6
|
+
result = []
|
7
|
+
|
8
|
+
registry = @op.plugins["core"].state[:contributions]
|
9
|
+
if registry.has_key? name
|
10
|
+
contributors = registry[name]
|
11
|
+
contributors.each do |contributor|
|
12
|
+
except = params["except"] || []
|
13
|
+
if except.include? contributor
|
14
|
+
puts "skipping contributor #{contributor} because of except"
|
15
|
+
next
|
16
|
+
end
|
17
|
+
|
18
|
+
$logger.debug "calling #{contributor} for contribution to #{name}"
|
19
|
+
#puts caller[0..10]
|
20
|
+
if caller.grep(/eval.*block in entity/).size > 2
|
21
|
+
$logger.debug "skipping contribution from #{contributor} because of loop"
|
22
|
+
else
|
23
|
+
$logger.debug "raw params: " + raw_params.inspect
|
24
|
+
short_name = contributor.to_s.split(".").last
|
25
|
+
result += @op.send(short_name.to_sym, raw_params)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
result
|
31
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
param! "name", :lookup => lambda { @op.list_commands.map { |x| x[:name] } }
|
2
|
+
|
3
|
+
run do |params, name|
|
4
|
+
editor = ENV["EDITOR"]
|
5
|
+
editor = "vim" # TODO if "config.default_editor"
|
6
|
+
raise "please set the EDITOR environment variable" unless editor
|
7
|
+
|
8
|
+
command_file = @op.command(name).source[:file_name]
|
9
|
+
system("#{editor} #{command_file}")
|
10
|
+
|
11
|
+
@op.reset
|
12
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
param! "name", :lookup => lambda { @op.list_commands.map { |x| x[:name] } }
|
2
|
+
|
3
|
+
run do |params|
|
4
|
+
puts
|
5
|
+
puts params["name"]
|
6
|
+
command = @op.commands[params["name"]]
|
7
|
+
if command.description
|
8
|
+
puts " #{command.description}"
|
9
|
+
end
|
10
|
+
|
11
|
+
puts
|
12
|
+
puts "syntax:"
|
13
|
+
|
14
|
+
minimal_syntax = command.short_name
|
15
|
+
maximal_syntax = command.short_name
|
16
|
+
command.params.each do |p|
|
17
|
+
example = if p == command.default_param
|
18
|
+
"<#{p[:name]}>"
|
19
|
+
else
|
20
|
+
"#{p[:name]}=<#{p[:name]}>"
|
21
|
+
end
|
22
|
+
minimal_syntax << " #{example}" if p[:mandatory]
|
23
|
+
maximal_syntax << " #{example}"
|
24
|
+
end
|
25
|
+
puts " #{minimal_syntax}"
|
26
|
+
puts " #{maximal_syntax}" unless minimal_syntax == maximal_syntax
|
27
|
+
|
28
|
+
if command.params.size > 0
|
29
|
+
puts
|
30
|
+
puts "parameters:"
|
31
|
+
max_param_length = command.params.map { |p| p[:name].length }.max
|
32
|
+
command.params.each do |p|
|
33
|
+
puts " %-#{max_param_length}s\t%s" % [p[:name], p[:description]]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
nil
|
38
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
def config(sym)
|
2
|
+
@plugin.config[sym]
|
3
|
+
end
|
4
|
+
|
5
|
+
def description(s)
|
6
|
+
@command.description = s
|
7
|
+
end
|
8
|
+
|
9
|
+
def param(name, options = {})
|
10
|
+
p = {
|
11
|
+
:name => name.to_s,
|
12
|
+
:multi => false,
|
13
|
+
:mandatory => false,
|
14
|
+
:default_param => false
|
15
|
+
}
|
16
|
+
|
17
|
+
if name.is_a? Symbol
|
18
|
+
# parameters whose names are symbols are resolved into entities
|
19
|
+
op = @plugin.op
|
20
|
+
|
21
|
+
entity_names = op.core.state[:entities].map { |entity| entity[:name] }
|
22
|
+
if entity_names.include? name.to_s
|
23
|
+
list_command_name = "list_#{name.to_s.pluralize(42)}"
|
24
|
+
p[:lookup] = lambda do |params|
|
25
|
+
# TODO :name is probably specific to the entity (key?)
|
26
|
+
op.send(list_command_name.to_sym).map { |x| x[:name] }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
@command.params << p.merge(options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def param!(name, options = {})
|
35
|
+
options.merge! :mandatory => true
|
36
|
+
param(name, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def show(options = {})
|
40
|
+
column_options = options.delete(:columns)
|
41
|
+
|
42
|
+
raise "unknown keyword #{options.keys.first}" if options.keys.length > 0
|
43
|
+
|
44
|
+
@command.show_options[:columns] = column_options
|
45
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# TODO remove?
|
2
|
+
def accept_contributions
|
3
|
+
#puts "#{@command.name} accepts contributions"
|
4
|
+
# TODO this init should happen earlier
|
5
|
+
#@op.plugins['core'].state[:contributions] ||= Hash.new { |h,k| h[k] = [] }
|
6
|
+
end
|
7
|
+
|
8
|
+
def contribute(options = {}, &block)
|
9
|
+
target = options[:to] || @command.short_name
|
10
|
+
#puts "#{@command.name} contributes to #{target}"
|
11
|
+
# TODO check that the command exists
|
12
|
+
@op.plugins['core'].state[:contributions][target] << @command.name
|
13
|
+
@command.block = block
|
14
|
+
end
|
15
|
+
|
16
|
+
def with_contributions(options = {}, &block)
|
17
|
+
raise "untested"
|
18
|
+
collected = @op.collect_contributions('name' => command_name, 'raw_params' => params)
|
19
|
+
params_with_contributions = params.merge(:contributions => collected)
|
20
|
+
list = block.call(params_with_contributions)
|
21
|
+
|
22
|
+
found = list.select { |x| x[key.to_sym] == params[key] }
|
23
|
+
if found && found.size > 0
|
24
|
+
Entity.new(@op, command_name, key, found.first)
|
25
|
+
else
|
26
|
+
raise "no such entity : #{params[key]} [#{command_name}]"
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'vop/entity'
|
2
|
+
require 'pp'
|
3
|
+
|
4
|
+
# if an entity doesn't define a block, by default
|
5
|
+
# it just passes through all contributions
|
6
|
+
def default_entity_block(params)
|
7
|
+
params['contributions']
|
8
|
+
end
|
9
|
+
|
10
|
+
def define_entity(name, key, options)
|
11
|
+
@op.plugins['core'].state[:entities] << {
|
12
|
+
name: name,
|
13
|
+
key: key,
|
14
|
+
options: options
|
15
|
+
}
|
16
|
+
end
|
17
|
+
|
18
|
+
def entity(key, options = {}, &block)
|
19
|
+
command_name = @command.short_name
|
20
|
+
|
21
|
+
# the entity command gets a mandatory param automatically, using the
|
22
|
+
# block defined by the entity as lookup
|
23
|
+
# TODO make the default block work
|
24
|
+
#list_block = block || default_entity_block
|
25
|
+
list_block = block
|
26
|
+
|
27
|
+
if options[:on]
|
28
|
+
param! options[:on]
|
29
|
+
end
|
30
|
+
|
31
|
+
param! key, :lookup => lambda { |params|
|
32
|
+
collected = @op.collect_contributions('name' => command_name, 'raw_params' => params)
|
33
|
+
params_with_contributions = params.merge(:contributions => collected)
|
34
|
+
the_list = list_block.call(params_with_contributions)
|
35
|
+
the_list
|
36
|
+
}
|
37
|
+
|
38
|
+
define_entity(command_name, key, options)
|
39
|
+
|
40
|
+
# entities generally accept contributions...
|
41
|
+
accept_contributions
|
42
|
+
|
43
|
+
# ...and they have a special run block that filters the correct row from
|
44
|
+
# the lookup list and returns an entity populated from that row
|
45
|
+
@command.block = lambda do |params|
|
46
|
+
collected = @op.collect_contributions('name' => command_name, 'raw_params' => params)
|
47
|
+
params_with_contributions = params.merge(:contributions => collected)
|
48
|
+
list = list_block.call(params_with_contributions)
|
49
|
+
|
50
|
+
found = list.select { |x| x[key.to_sym] == params[key] }
|
51
|
+
if found && found.size > 0
|
52
|
+
Entity.new(@op, command_name, key, found.first)
|
53
|
+
else
|
54
|
+
raise "no such entity : #{params[key]} [#{command_name}]"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
File without changes
|
@@ -0,0 +1,17 @@
|
|
1
|
+
description "returns a list of commands with descriptions"
|
2
|
+
|
3
|
+
param 'plugin', :description => 'list of plugins to filter by',
|
4
|
+
:multi => true,
|
5
|
+
:lookup => lambda { |params| @op.list_plugins.map { |x| x[:name] } },
|
6
|
+
:default_param => true
|
7
|
+
|
8
|
+
run do |params|
|
9
|
+
@op.commands.select do |name, command|
|
10
|
+
not params.has_key?('plugin') or params['plugin'].include?(command.plugin.name)
|
11
|
+
end.map do |name, command|
|
12
|
+
{
|
13
|
+
:name => name,
|
14
|
+
:description => command.description || ''
|
15
|
+
}
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
param! 'name'
|
2
|
+
param! 'plugin', :lookup => lambda { @op.list_plugins.map { |x| x[:name] } }
|
3
|
+
|
4
|
+
run do |params|
|
5
|
+
pp params
|
6
|
+
plugin = @op.list_plugins.select { |x| x[:name] == params['plugin'] }.first
|
7
|
+
|
8
|
+
command_file = File.join(plugin[:path], 'commands', "#{params['name']}.rb")
|
9
|
+
File.write(command_file, "run { 42 }")
|
10
|
+
|
11
|
+
@op.reset
|
12
|
+
|
13
|
+
nil
|
14
|
+
end
|