vop 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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