vop 0.3.5 → 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci.yml +39 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile.lock +34 -47
  5. data/README.md +118 -16
  6. data/bin/sidekiq.sh +10 -0
  7. data/exe/vop +2 -2
  8. data/lib/core/cache/cache.plugin +0 -0
  9. data/lib/core/cache/commands/invalidate_cache.rb +9 -0
  10. data/lib/core/{structure → meta}/commands/list_commands.rb +2 -1
  11. data/lib/core/meta/commands/list_filters.rb +3 -0
  12. data/lib/core/meta/commands/list_plugins.rb +3 -0
  13. data/lib/core/meta/commands/new_plugin.rb +3 -7
  14. data/lib/core/shell/commands/detail.rb +21 -0
  15. data/lib/core/shell/commands/edit.rb +5 -2
  16. data/lib/core/shell/commands/help.rb +1 -1
  17. data/lib/core/shell/commands/source.rb +10 -4
  18. data/lib/core/structure/commands/collect_contributions.rb +8 -2
  19. data/lib/core/structure/commands/generate_entity_commands.rb +19 -10
  20. data/lib/core/structure/commands/generate_invalidation_commands.rb +19 -9
  21. data/lib/core/structure/commands/list_contribution_targets.rb +9 -0
  22. data/lib/core/structure/commands/list_contributors.rb +1 -1
  23. data/lib/core/structure/structure.plugin +1 -1
  24. data/lib/vop/objects/chain.rb +6 -3
  25. data/lib/vop/objects/command.rb +14 -5
  26. data/lib/vop/objects/command_param.rb +22 -0
  27. data/lib/vop/objects/entities.rb +8 -8
  28. data/lib/vop/objects/entity.rb +57 -16
  29. data/lib/vop/objects/entity_definition.rb +22 -0
  30. data/lib/vop/objects/plugin.rb +46 -4
  31. data/lib/vop/objects/request.rb +9 -5
  32. data/lib/vop/parts/dependency_resolver.rb +0 -4
  33. data/lib/vop/parts/entity_loader.rb +0 -3
  34. data/lib/vop/parts/executor.rb +33 -8
  35. data/lib/vop/parts/plugin_finder.rb +6 -16
  36. data/lib/vop/search_path.rb +12 -0
  37. data/lib/vop/shell/shell.rb +134 -87
  38. data/lib/vop/shell/shell_formatter.rb +32 -17
  39. data/lib/vop/shell/shell_input_readline.rb +3 -0
  40. data/lib/vop/shell/shell_input_testable.rb +9 -3
  41. data/lib/vop/syntax/command_syntax.rb +22 -17
  42. data/lib/vop/syntax/entity_syntax.rb +21 -6
  43. data/lib/vop/syntax/plugin_syntax.rb +6 -0
  44. data/lib/vop/util/pluralizer.rb +9 -1
  45. data/lib/vop/version.rb +1 -1
  46. data/lib/vop/vop.rb +70 -44
  47. data/lib/vop.rb +11 -1
  48. data/vop.gemspec +8 -6
  49. metadata +103 -28
  50. data/lib/core/meta/commands/search_gems_for_plugins.rb +0 -38
  51. data/lib/core/meta/commands/search_path.rb +0 -6
  52. data/lib/core/structure/commands/list_plugins.rb +0 -3
  53. data/lib/vop/util/worker.rb +0 -24
@@ -1,4 +1,3 @@
1
- require "readline"
2
1
  require_relative "shell_formatter"
3
2
  require_relative "shell_input"
4
3
  require_relative "shell_input_readline"
@@ -8,6 +7,7 @@ module Vop
8
7
  class Shell
9
8
 
10
9
  attr_reader :context
10
+ attr_reader :last_response
11
11
 
12
12
  def initialize(op, input = nil)
13
13
  @op = op
@@ -15,7 +15,7 @@ module Vop
15
15
 
16
16
  @formatter = ShellFormatter.new
17
17
 
18
- # TODO for testing
18
+ # override for testing
19
19
  if input.nil?
20
20
  input = ShellInputReadline.new(method(:tab_completion))
21
21
  end
@@ -45,43 +45,48 @@ module Vop
45
45
  puts
46
46
  print @prompt
47
47
  else
48
- puts "\nbye"
48
+ puts "\n"
49
49
  exit
50
50
  end
51
51
  end
52
52
 
53
- def mix_arguments_and_context
54
- result = @arguments
53
+ def mix_arguments_and_context(command = nil, arguments = nil)
54
+ result = arguments || @arguments
55
55
  @context.each do |k,v|
56
- param = @command.param(k)
57
- if param && param.wants_context
58
- result[k] = @context[k]
56
+ cmd = command || @command
57
+ if cmd
58
+ param = (cmd).param(k)
59
+ if param && param.wants_context
60
+ result[k] = @context[k]
61
+ end
59
62
  end
60
63
  end
61
64
  result
62
65
  end
63
66
 
64
- def maybe_execute
65
- mandatory = @command.mandatory_params
66
-
67
- missing_mandatory_params = @command.mandatory_params.delete_if do |param|
68
- @arguments.keys.include?(param.name) ||
67
+ def missing_mandatory_params(command = @command, arguments = @arguments)
68
+ command.mandatory_params.delete_if do |param|
69
+ arguments.keys.include?(param.name) ||
69
70
  (@context.keys.include?(param.name) && param.wants_context)
70
71
  end
72
+ end
71
73
 
72
- if missing_mandatory_params.size > 0
73
- $logger.debug "missing params : #{missing_mandatory_params.map(&:name)}"
74
- end
75
-
74
+ def maybe_execute
76
75
  if missing_mandatory_params.size > 0
77
76
  @missing_params = missing_mandatory_params
78
77
  @prompt = "#{@command.short_name}.#{@missing_params.first.name} ? "
79
78
  else
80
79
  begin
81
- request = Request.new(@op, @command.short_name, @arguments, @context)
80
+ request = @op.prepare_request(@command.short_name, @arguments, @context, @command.short_name)
82
81
  request.shell = self
83
82
  response = @op.execute_request(request)
84
83
 
84
+ # log the last response for the "detail" command
85
+ unless @command.short_name == "detail"
86
+ @last_response = response
87
+ end
88
+
89
+ # mix context changes from the response into the local context
85
90
  @context.merge! response.context
86
91
 
87
92
  display_type = @formatter.analyze(request, response)
@@ -95,65 +100,92 @@ module Vop
95
100
  end
96
101
 
97
102
  def accept_param(line)
98
- current_param = @missing_params.shift
99
- $logger.debug "value for param #{current_param.name} : #{line}"
100
- @arguments[current_param.name] = line
103
+ unless line.nil?
104
+ current_param = @missing_params.shift
105
+ $logger.debug "value for param #{current_param.name} : #{line}"
106
+ @arguments[current_param.name] = line
101
107
 
102
- maybe_execute
108
+ maybe_execute
109
+ end
103
110
  end
104
111
 
105
- def parse_command_line(args)
106
- result = {}
107
- unless args.empty?
108
- args.each do |token|
109
- if token.include? "="
110
- (key, value) = token.split("=")
111
- result[key] = value
112
- else
113
- default_param = @command.default_param
114
- if default_param
115
- result[default_param.name] = args
116
- end
117
- end
112
+ def is_special?(command)
113
+ command.start_with?('$') || command.start_with?('@') || command.end_with?('?')
114
+ end
115
+
116
+ def handle_special(command)
117
+ if command.start_with?('$')
118
+ if command.start_with?('$vop')
119
+ puts "executing #{command}"
120
+ puts eval command
121
+ else
122
+ puts "unknown $-command #{command} - try '$vop' maybe?"
123
+ end
124
+ elsif command.start_with?('@')
125
+ if command.start_with?('@op')
126
+ puts "executing #{command}"
127
+ puts eval command
128
+ else
129
+ puts "unknown @-command #{command} - try '@op' maybe?"
118
130
  end
119
131
  end
120
- result
121
132
  end
122
133
 
123
- def parse_and_execute(command_line)
134
+ def parse(command_line)
124
135
  (command, *args) = command_line.split
125
136
 
137
+ arguments = {}
126
138
  if command
127
139
  $logger.debug "command : #{command}, args : #{args}"
128
- if command.start_with?('$')
129
- if command.start_with?('$vop')
130
- puts "executing #{command}"
131
- puts eval command
132
- else
133
- puts "unknown $-command #{command} - try '$vop' maybe?"
134
- end
135
- elsif command.start_with?('@')
136
- if command.start_with?('@op')
137
- puts "executing #{command}"
138
- puts eval command
139
- else
140
- puts "unknown @-command #{command} - try '@op' maybe?"
140
+
141
+ if command.end_with?("??")
142
+ target_command = command[0..-3]
143
+ command = "source"
144
+ arguments["name"] = target_command
145
+ elsif command.end_with?("?")
146
+ target_command = command[0..-2]
147
+ command = "help"
148
+ arguments["name"] = target_command
149
+ end
150
+
151
+ known_commands = @op.commands.keys
152
+ if known_commands.include? command
153
+ cmd = @op.commands[command]
154
+
155
+ unless args.empty? || is_special?(command)
156
+ args.each do |token|
157
+ if token.include? "="
158
+ (key, value) = token.split("=")
159
+ arguments[key] = value
160
+ else
161
+ default_param = cmd.default_param(mix_arguments_and_context)
162
+ if default_param
163
+ arguments[default_param.name] = args
164
+ end
165
+ end
166
+ end
141
167
  end
168
+ [command, cmd, arguments]
142
169
  else
143
- if command.end_with?("?")
144
- help_command = command[0..-2]
145
- command = "help"
146
- args << "name=#{help_command}"
147
- end
170
+ [command, nil, arguments]
171
+ end
172
+ end
173
+ end
148
174
 
175
+ def parse_and_execute(command_line)
176
+ command, cmd, arguments = parse(command_line)
177
+
178
+ if command
179
+ $logger.debug "command : #{command}, args : #{arguments}"
180
+ if is_special?(command)
181
+ handle_special(command_line)
182
+ else
149
183
  if command == "exit"
150
184
  @input.exit
151
185
  else
152
- known_commands = @op.commands.keys
153
- if known_commands.include? command
154
- @command = @op.commands[command]
155
- @arguments = parse_command_line(args)
156
-
186
+ if cmd
187
+ @command = cmd
188
+ @arguments = arguments
157
189
  maybe_execute
158
190
  else
159
191
  puts "unknown command '#{command}'"
@@ -161,47 +193,62 @@ module Vop
161
193
  end
162
194
  end
163
195
  end
164
-
165
196
  end
166
197
 
167
- def tab_completion(s)
168
- lookups = []
198
+ def complete_for_command(s)
199
+ current_param = @missing_params.first
200
+ if current_param && current_param.options.has_key?(:lookup)
201
+ begin
202
+ lookup_block = current_param.options[:lookup]
169
203
 
170
- if @command
171
- current_param = @missing_params.first
172
- if current_param && current_param.options.has_key?(:lookup)
173
- begin
174
- lookup_block = current_param.options[:lookup]
175
-
176
- # the lookup block might want the previously collected params as input
177
- lookups = if lookup_block.arity > 0
178
- params_for_lookup = mix_arguments_and_context
179
- lookup_block.call(params_for_lookup)
180
- else
181
- lookup_block.call()
182
- end
183
- rescue => detail
184
- $logger.error "problem loading lookup values for #{current_param.name} : #{detail.message}"
204
+ # the lookup block might want the previously collected params as input
205
+ lookups = if lookup_block&.arity > 0
206
+ params_for_lookup = mix_arguments_and_context
207
+ lookup_block.call(params_for_lookup)
208
+ else
209
+ lookup_block.call()
185
210
  end
211
+ lookups.grep /^#{Regexp.escape(s)}/
212
+ rescue => detail
213
+ $logger.error "problem loading lookup values for #{current_param.name} : #{detail.message}"
214
+ end
215
+ end
216
+ end
217
+
218
+ def complete_command_line(s)
219
+ potential_command, potential_cmd, potential_args = parse(s)
220
+ #$logger.debug "? >>#{potential_cmd}<< (#{potential_args}) [#{Readline.line_buffer}]"
221
+ if potential_cmd
222
+ default_param = potential_cmd.default_param
223
+ if default_param
224
+ lookups = default_param.lookup(mix_arguments_and_context)
225
+ lookups
226
+ .grep(/^#{Regexp.escape(s.split.last)}/)
227
+ .map do |lookup|
228
+ "#{potential_command} #{lookup}"
229
+ end
186
230
  end
187
231
  else
188
- lookups = @op.commands.keys.sort
232
+ lookups = @op.commands.keys.sort.grep /^#{Regexp.escape(s)}/
189
233
  end
190
234
 
191
- lookups.grep /^#{Regexp.escape(s)}/
192
235
  end
193
236
 
194
- def do_it(command_line = nil)
195
- #Readline.completion_append_character = ""
196
- #Readline.completion_proc = method(:tab_completion)
237
+ def tab_completion(s)
238
+ if @command
239
+ complete_for_command(s)
240
+ else
241
+ complete_command_line(s)
242
+ end
243
+ end
197
244
 
198
- if command_line
245
+ def do_it(command_line = nil)
246
+ if command_line && command_line != ""
199
247
  parse_and_execute(command_line)
200
248
  else
201
249
  while line = @input.read(@prompt)
202
- #while line = Readline.readline(@prompt, true)
203
250
  if @command
204
- # if a command has already been selected, we ask for missing params
251
+ # if a command has already been selected, ask for missing params
205
252
  accept_param(line)
206
253
  else
207
254
  # otherwise input is treated as regular command line (command + args)
@@ -213,7 +260,7 @@ module Vop
213
260
 
214
261
  def self.run(op = nil, command_line = nil)
215
262
  if op.nil?
216
- op = Vop.new
263
+ op = Vop.new(origin: "shell:#{Process.pid}@#{`hostname`.strip}")
217
264
  end
218
265
  self.new(op).do_it(command_line)
219
266
  end
@@ -35,10 +35,11 @@ module Vop
35
35
  command = request.command
36
36
  show_options = command.show_options
37
37
 
38
- result = case display_type
38
+ case display_type
39
39
  when :table
40
40
  columns_to_display =
41
41
  if show_options[:columns]
42
+ # TODO validate all columns exist?
42
43
  show_options[:columns]
43
44
  else
44
45
  # TODO this is not optimal - what if the second row has more keys than the first?
@@ -54,8 +55,9 @@ module Vop
54
55
  data.each do |row|
55
56
  values = [ ]
56
57
  columns_to_display.each do |key|
57
- values << (row[key.to_s] || row[key.to_sym])
58
- end
58
+ potential_value = row[key.to_s] || row[key.to_sym]
59
+ values << potential_value
60
+ end unless row.nil?
59
61
  rearranged << values
60
62
  end
61
63
 
@@ -79,18 +81,33 @@ module Vop
79
81
  "#{k} : #{v}"
80
82
  end.join("\n")
81
83
  when :entity_list
82
- data.sort_by { |e| e.id }.map do |entity|
83
- attributes = entity.data.sort_by do |x|
84
-
85
- end.map do |key, value|
86
- if key == entity.key
87
- nil
88
- else
89
- "#{key} : #{value}"
90
- end
91
- end.compact.join("\n ")
92
- "[#{entity.type}] #{entity.id}\n #{attributes}"
93
- end.join("\n")
84
+ sorted = data.sort_by { |e| e.id }
85
+
86
+ columns = if show_options[:columns]
87
+ show_options[:columns]
88
+ else
89
+ blacklisted_keys = %w|name params plugin_name|
90
+ all_keys = sorted.map { |x| x.data.keys }.flatten.uniq
91
+ all_keys.delete_if { |x| blacklisted_keys.include? x }
92
+ end
93
+
94
+ headers = [ sorted.first.key ] + columns
95
+
96
+ rows = sorted.map do |entity|
97
+ row = [ entity.id ]
98
+
99
+ columns.each do |column|
100
+ value = entity.data[column] || ""
101
+ row << (value.respond_to?(to_s) ? value.to_s[0..49] : value)
102
+ end
103
+
104
+ row
105
+ end
106
+
107
+ Terminal::Table.new(
108
+ rows: rows,
109
+ headings: headers
110
+ )
94
111
  when :entity
95
112
  entity = data
96
113
  "[#{entity.type}] #{entity.id}"
@@ -101,8 +118,6 @@ module Vop
101
118
  else
102
119
  raise "unknown display type #{display_type}"
103
120
  end
104
-
105
- result
106
121
  end
107
122
 
108
123
  end
@@ -1,3 +1,5 @@
1
+ require "readline"
2
+
1
3
  module Vop
2
4
 
3
5
  class ShellInputReadline
@@ -5,6 +7,7 @@ module Vop
5
7
  def initialize(completion_method)
6
8
  Readline.completion_append_character = ""
7
9
  Readline.completion_proc = completion_method
10
+ Readline.completer_word_break_characters = "\t\n\"\\'\`@$><=;|&{(" # default, but without space
8
11
  end
9
12
 
10
13
  def read(prompt)
@@ -3,17 +3,23 @@ module Vop
3
3
  class TestableShellInput
4
4
 
5
5
  attr_accessor :answers
6
- attr_reader :exit
6
+ attr_reader :exit, :prompt
7
+ attr_accessor :fail_if_out_of_answers
7
8
 
8
9
  def initialize
9
10
  @answers = []
10
11
  @exit = false
12
+ @prompt = nil
13
+ @fail_if_out_of_answers = true
11
14
  end
12
15
 
13
16
  def read(prompt)
17
+ @prompt = prompt
14
18
  answer = @answers.shift
15
- if answer.nil?
16
- raise "no more pre-defined answers (asked for '#{prompt}')"
19
+ if answer.nil? && @fail_if_out_of_answers
20
+ unless @exit
21
+ raise "no more pre-defined answers (asked for '#{prompt}')"
22
+ end
17
23
  end
18
24
  answer
19
25
  end
@@ -2,24 +2,17 @@ module Vop
2
2
 
3
3
  module CommandSyntax
4
4
 
5
- def run(&block)
6
- @command.block = block
7
- end
8
-
9
5
  def description(s)
10
6
  @command.description = s
11
7
  end
12
8
 
13
- def resolve_options_string(options)
14
- if options.is_a? String
15
- options = {
16
- description: options
17
- }
18
- end
19
- options
9
+ def run(&block)
10
+ @command.block = block
20
11
  end
21
12
 
22
- def param(name, options = {})
13
+ def param(name, options = {}, more_options = {})
14
+ options = resolve_options_string(options).merge(more_options)
15
+
23
16
  if name.is_a? Symbol
24
17
  key = "name" # default for select_machine
25
18
  entity = @op.entities.values.select { |x| x.short_name == name.to_s }.first
@@ -38,18 +31,15 @@ module Vop
38
31
  name = name.to_s
39
32
  end
40
33
 
41
- options = resolve_options_string(options)
42
-
43
34
  @command.add_param(name, options)
44
35
  end
45
36
 
46
- def param!(name, options = {})
47
- options = resolve_options_string(options)
37
+ def param!(name, options = {}, more_options = {})
38
+ options = resolve_options_string(options).merge(more_options)
48
39
  options.merge! mandatory: true
49
40
  param(name, options)
50
41
  end
51
42
 
52
- # TODO does not really work yet
53
43
  def block_param(name = "block", options = {})
54
44
  options.merge! block: true
55
45
  param(name, options)
@@ -69,6 +59,10 @@ module Vop
69
59
  @command.allows_extra = true
70
60
  end
71
61
 
62
+ def dont_log
63
+ @command.dont_log = true
64
+ end
65
+
72
66
  def show(options = {})
73
67
  @command.show_options[:columns] = options.delete(:columns)
74
68
  @command.show_options[:display_type] = options.delete(:display_type)
@@ -89,6 +83,17 @@ module Vop
89
83
  @command.invalidation_block = block
90
84
  end
91
85
 
86
+ private
87
+
88
+ def resolve_options_string(options)
89
+ if options.is_a? String
90
+ options = {
91
+ description: options
92
+ }
93
+ end
94
+ options
95
+ end
96
+
92
97
  end
93
98
 
94
99
  end
@@ -2,18 +2,20 @@ module Vop
2
2
 
3
3
  module EntitySyntax
4
4
 
5
+ def description(s)
6
+ @entity.description = s
7
+ end
8
+
5
9
  def key(key)
6
10
  @entity.key = key
7
11
  end
8
12
 
9
13
  def entity(options = { key: "name" }, &block)
10
- if block
11
- run(&block)
12
- end
14
+ run(&block)
13
15
  end
14
16
 
15
17
  def run(&block)
16
- @entity.block = block
18
+ @entity.block = block if block
17
19
  end
18
20
 
19
21
  def on(other_entity)
@@ -26,8 +28,21 @@ module Vop
26
28
 
27
29
  raise "unknown keyword #{options.keys.first}" if options.keys.length > 0
28
30
 
29
- @entity.show_options[:columns] = column_options
30
- @entity.show_options[:display_type] = display_type
31
+ @entity.show_options[:columns] = column_options if column_options
32
+ @entity.show_options[:display_type] = display_type if display_type
33
+ end
34
+
35
+ def invalidate(&block)
36
+ @entity.invalidation_block = block
37
+ end
38
+
39
+ def contribute(options, &block)
40
+ raise "missing option 'to'" unless options.has_key?(:to)
41
+ @op.register_contributor(
42
+ command_name: options[:to],
43
+ contributor: @entity.name.to_s.carefully_pluralize
44
+ )
45
+ run(&block)
31
46
  end
32
47
 
33
48
  end
@@ -42,6 +42,12 @@ module Vop
42
42
  end
43
43
  end
44
44
 
45
+ # TODO: support version requirements
46
+ def depends_on_gem(gem, **options)
47
+ $logger.debug "plugin #{@plugin.name} depends on gem #{gem}"
48
+ @plugin.external_dependencies[:gem] << [gem, options]
49
+ end
50
+
45
51
  def on(hook_sym, &block)
46
52
  @plugin.hook(hook_sym, &block)
47
53
  end
@@ -2,7 +2,7 @@ begin
2
2
  require "active_support/inflector"
3
3
  rescue Exception => e
4
4
  message = "active_support inflector cannot be loaded - pluralization results may deviate : #{e.message}"
5
- puts message
5
+ #puts message
6
6
  end
7
7
 
8
8
  module Vop
@@ -19,6 +19,14 @@ module Vop
19
19
  end
20
20
  end
21
21
 
22
+ def carefully_singularize
23
+ begin
24
+ self.singularize
25
+ rescue
26
+ "#{self[0..-2]}"
27
+ end
28
+ end
29
+
22
30
  end
23
31
 
24
32
  end
data/lib/vop/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Vop
2
- VERSION = "0.3.5"
2
+ VERSION = "0.3.6"
3
3
  end