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.
Files changed (54) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +50 -0
  3. data/Gemfile +3 -0
  4. data/Gemfile.lock +66 -0
  5. data/README.md +2 -0
  6. data/Rakefile +6 -0
  7. data/bin/vop.rb +28 -0
  8. data/bin/vop.sh +4 -0
  9. data/exe/vop +28 -0
  10. data/lib/vop.rb +242 -0
  11. data/lib/vop/command.rb +168 -0
  12. data/lib/vop/command_loader.rb +47 -0
  13. data/lib/vop/entity.rb +61 -0
  14. data/lib/vop/loader.rb +35 -0
  15. data/lib/vop/plugin.rb +141 -0
  16. data/lib/vop/plugin_loader.rb +88 -0
  17. data/lib/vop/plugins/core/commands/clear_context.rb +3 -0
  18. data/lib/vop/plugins/core/commands/collect_contributions.rb +31 -0
  19. data/lib/vop/plugins/core/commands/edit.rb +12 -0
  20. data/lib/vop/plugins/core/commands/help.rb +38 -0
  21. data/lib/vop/plugins/core/commands/identity.rb +4 -0
  22. data/lib/vop/plugins/core/commands/list_contributors.rb +8 -0
  23. data/lib/vop/plugins/core/commands/list_entities.rb +3 -0
  24. data/lib/vop/plugins/core/commands/pry.rb +9 -0
  25. data/lib/vop/plugins/core/commands/reset.rb +5 -0
  26. data/lib/vop/plugins/core/commands/show_context.rb +3 -0
  27. data/lib/vop/plugins/core/commands/source.rb +5 -0
  28. data/lib/vop/plugins/core/commands/system_call.rb +5 -0
  29. data/lib/vop/plugins/core/core.plugin +4 -0
  30. data/lib/vop/plugins/core/helpers/command_loader/command_syntax.rb +45 -0
  31. data/lib/vop/plugins/core/helpers/command_loader/contributions.rb +28 -0
  32. data/lib/vop/plugins/core/helpers/command_loader/entities.rb +57 -0
  33. data/lib/vop/plugins/core/helpers/helper.rb +3 -0
  34. data/lib/vop/plugins/core/helpers/plugin_loader/plugin_syntax.rb +0 -0
  35. data/lib/vop/plugins/meta/commands/add_search_path.rb +6 -0
  36. data/lib/vop/plugins/meta/commands/delete_plugin.rb +13 -0
  37. data/lib/vop/plugins/meta/commands/list_commands.rb +17 -0
  38. data/lib/vop/plugins/meta/commands/list_plugins.rb +8 -0
  39. data/lib/vop/plugins/meta/commands/new_command.rb +14 -0
  40. data/lib/vop/plugins/meta/commands/new_plugin.rb +25 -0
  41. data/lib/vop/plugins/meta/commands/show_search_path.rb +3 -0
  42. data/lib/vop/plugins/meta/commands/who_provides.rb +5 -0
  43. data/lib/vop/plugins/meta/meta.plugin +1 -0
  44. data/lib/vop/plugins/ssh/commands/scp.rb +11 -0
  45. data/lib/vop/plugins/ssh/commands/ssh.rb +19 -0
  46. data/lib/vop/plugins/ssh/ssh.plugin +1 -0
  47. data/lib/vop/shell.rb +52 -0
  48. data/lib/vop/shell/backend.rb +28 -0
  49. data/lib/vop/shell/base_shell.rb +112 -0
  50. data/lib/vop/shell/formatter.rb +46 -0
  51. data/lib/vop/shell/vop_shell_backend.rb +257 -0
  52. data/lib/vop/version.rb +3 -0
  53. data/vop.gemspec +31 -0
  54. metadata +223 -0
@@ -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