dtk-client 0.5.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +5 -0
  3. data/Gemfile_dev +12 -0
  4. data/README.md +78 -0
  5. data/bin/dtk +54 -0
  6. data/bin/dtk-shell +15 -0
  7. data/dtk-client.gemspec +49 -0
  8. data/lib/auxiliary.rb +13 -0
  9. data/lib/bundler_monkey_patch.rb +9 -0
  10. data/lib/client.rb +48 -0
  11. data/lib/command_helper.rb +16 -0
  12. data/lib/command_helpers/git_repo.rb +391 -0
  13. data/lib/command_helpers/jenkins_client/config_xml.rb +271 -0
  14. data/lib/command_helpers/jenkins_client.rb +91 -0
  15. data/lib/command_helpers/service_importer.rb +99 -0
  16. data/lib/command_helpers/service_link.rb +18 -0
  17. data/lib/command_helpers/ssh_processing.rb +43 -0
  18. data/lib/commands/common/thor/assembly_workspace.rb +1089 -0
  19. data/lib/commands/common/thor/clone.rb +39 -0
  20. data/lib/commands/common/thor/common.rb +34 -0
  21. data/lib/commands/common/thor/edit.rb +168 -0
  22. data/lib/commands/common/thor/list_diffs.rb +84 -0
  23. data/lib/commands/common/thor/pull_clone_changes.rb +11 -0
  24. data/lib/commands/common/thor/pull_from_remote.rb +99 -0
  25. data/lib/commands/common/thor/purge_clone.rb +26 -0
  26. data/lib/commands/common/thor/push_clone_changes.rb +45 -0
  27. data/lib/commands/common/thor/push_to_remote.rb +45 -0
  28. data/lib/commands/common/thor/reparse.rb +36 -0
  29. data/lib/commands/common/thor/set_required_params.rb +29 -0
  30. data/lib/commands/common/thor/task_status.rb +81 -0
  31. data/lib/commands/thor/account.rb +213 -0
  32. data/lib/commands/thor/assembly.rb +329 -0
  33. data/lib/commands/thor/attribute.rb +62 -0
  34. data/lib/commands/thor/component.rb +52 -0
  35. data/lib/commands/thor/component_module.rb +829 -0
  36. data/lib/commands/thor/component_template.rb +153 -0
  37. data/lib/commands/thor/dependency.rb +18 -0
  38. data/lib/commands/thor/developer.rb +105 -0
  39. data/lib/commands/thor/dtk.rb +117 -0
  40. data/lib/commands/thor/library.rb +107 -0
  41. data/lib/commands/thor/node.rb +411 -0
  42. data/lib/commands/thor/node_group.rb +211 -0
  43. data/lib/commands/thor/node_template.rb +88 -0
  44. data/lib/commands/thor/project.rb +17 -0
  45. data/lib/commands/thor/provider.rb +155 -0
  46. data/lib/commands/thor/repo.rb +35 -0
  47. data/lib/commands/thor/service.rb +656 -0
  48. data/lib/commands/thor/service_module.rb +806 -0
  49. data/lib/commands/thor/state_change.rb +10 -0
  50. data/lib/commands/thor/target.rb +94 -0
  51. data/lib/commands/thor/task.rb +100 -0
  52. data/lib/commands/thor/utils.rb +4 -0
  53. data/lib/commands/thor/workspace.rb +437 -0
  54. data/lib/commands.rb +40 -0
  55. data/lib/config/cacert.pem +3785 -0
  56. data/lib/config/client.conf.header +18 -0
  57. data/lib/config/configuration.rb +82 -0
  58. data/lib/config/default.conf +14 -0
  59. data/lib/config/disk_cacher.rb +60 -0
  60. data/lib/configurator.rb +92 -0
  61. data/lib/context_router.rb +23 -0
  62. data/lib/core.rb +460 -0
  63. data/lib/domain/git_adapter.rb +221 -0
  64. data/lib/domain/response.rb +234 -0
  65. data/lib/dtk-client/version.rb +3 -0
  66. data/lib/dtk_constants.rb +23 -0
  67. data/lib/dtk_logger.rb +96 -0
  68. data/lib/error.rb +74 -0
  69. data/lib/git-logs/git.log +0 -0
  70. data/lib/parser/adapters/option_parser.rb +53 -0
  71. data/lib/parser/adapters/thor/common_option_defs.rb +12 -0
  72. data/lib/parser/adapters/thor.rb +509 -0
  73. data/lib/require_first.rb +87 -0
  74. data/lib/search_hash.rb +27 -0
  75. data/lib/shell/context.rb +975 -0
  76. data/lib/shell/context_aux.rb +29 -0
  77. data/lib/shell/domain.rb +447 -0
  78. data/lib/shell/header_shell.rb +27 -0
  79. data/lib/shell/help_monkey_patch.rb +221 -0
  80. data/lib/shell/interactive_wizard.rb +233 -0
  81. data/lib/shell/parse_monkey_patch.rb +22 -0
  82. data/lib/shell/status_monitor.rb +105 -0
  83. data/lib/shell.rb +219 -0
  84. data/lib/util/console.rb +143 -0
  85. data/lib/util/dtk_puppet.rb +46 -0
  86. data/lib/util/os_util.rb +265 -0
  87. data/lib/view_processor/augmented_simple_list.rb +27 -0
  88. data/lib/view_processor/hash_pretty_print.rb +106 -0
  89. data/lib/view_processor/simple_list.rb +139 -0
  90. data/lib/view_processor/table_print.rb +277 -0
  91. data/lib/view_processor.rb +112 -0
  92. data/puppet/manifests/init.pp +72 -0
  93. data/puppet/manifests/params.pp +16 -0
  94. data/puppet/r8meta.puppet.yml +18 -0
  95. data/puppet/templates/bash_profile.erb +2 -0
  96. data/puppet/templates/client.conf.erb +1 -0
  97. data/puppet/templates/dtkclient.erb +2 -0
  98. data/spec/assembly_spec.rb +50 -0
  99. data/spec/assembly_template_spec.rb +51 -0
  100. data/spec/component_template_spec.rb +40 -0
  101. data/spec/dependency_spec.rb +6 -0
  102. data/spec/dtk_shell_spec.rb +13 -0
  103. data/spec/dtk_spec.rb +33 -0
  104. data/spec/lib/spec_helper.rb +10 -0
  105. data/spec/lib/spec_thor.rb +105 -0
  106. data/spec/module_spec.rb +35 -0
  107. data/spec/node_spec.rb +43 -0
  108. data/spec/node_template_spec.rb +25 -0
  109. data/spec/project_spec.rb +6 -0
  110. data/spec/repo_spec.rb +7 -0
  111. data/spec/response_spec.rb +52 -0
  112. data/spec/service_spec.rb +41 -0
  113. data/spec/state_change_spec.rb +7 -0
  114. data/spec/table_print_spec.rb +48 -0
  115. data/spec/target_spec.rb +57 -0
  116. data/spec/task_spec.rb +28 -0
  117. data/views/assembly/augmented_simple_list.rb +12 -0
  118. data/views/assembly_template/augmented_simple_list.rb +12 -0
  119. data/views/list_task/augmented_simple_list.rb +12 -0
  120. metadata +351 -0
@@ -0,0 +1,975 @@
1
+ require File.expand_path('../commands/thor/dtk', File.dirname(__FILE__))
2
+ require File.expand_path('../auxiliary', File.dirname(__FILE__))
3
+ require 'json'
4
+
5
+ module DTK
6
+ module Shell
7
+
8
+ class Context
9
+ include DTK::Client::Auxiliary
10
+
11
+ # client commands
12
+ CLIENT_COMMANDS = ['cc','exit','clear','pushc','popc','dirs','help']
13
+ # CLIENT_COMMANDS = ['cc','exit','clear','help']
14
+ DEV_COMMANDS = ['restart']
15
+ DTK_ROOT_PROMPT = "dtk:/>"
16
+ COMMAND_HISTORY_LIMIT = 200
17
+ HISTORY_LOCATION = DTK::Client::OsUtil.dtk_local_folder + "shell_history"
18
+ ROOT_TASKS = DTK::Client::Dtk.task_names
19
+ ALL_COMMANDS = ROOT_TASKS + DTK::Client::Dtk.additional_entities
20
+ IDENTIFIERS_ONLY = ['cc','cd','pushc']
21
+
22
+
23
+ SYM_LINKS = [
24
+ { :alias => :workspace, :path => 'workspace/workspace' }
25
+ ]
26
+
27
+ # current holds context (list of commands) for active context e.g. dtk:\library>
28
+ attr_accessor :current
29
+ attr_accessor :active_context
30
+ attr_accessor :cached_tasks
31
+ attr_accessor :dirs
32
+
33
+
34
+ def initialize(skip_caching=false)
35
+ @cached_tasks, @dirs = DTK::Shell::CachedTasks.new, []
36
+ @active_context = ActiveContext.new
37
+ @previous_context = nil
38
+
39
+ # member used to hold current commands loaded for current command
40
+ @context_commands = []
41
+ @conn = DTK::Client::Session.get_connection()
42
+
43
+ # if connection parameters are not set up properly, print warning and exit dtk_shell
44
+ exit if validate_connection(@conn)
45
+
46
+ unless skip_caching
47
+ @cached_tasks.store('dtk', ROOT_TASKS.sort)
48
+
49
+ ALL_COMMANDS.each do |task_name|
50
+ # we exclude help since there is no command class for it
51
+ next if task_name.eql? "help"
52
+ Context.require_command_class(task_name)
53
+
54
+ get_latest_tasks(task_name)
55
+ end
56
+ end
57
+ end
58
+
59
+ def self.get_command_class(command_name)
60
+ ::DTK::Client::OsUtil.get_dtk_class(command_name)
61
+ end
62
+
63
+ def self.require_command_class(command_name)
64
+ # normalize to file_names
65
+ file_name = command_name.gsub('-','_')
66
+ require File.expand_path("../commands/thor/#{file_name}", File.dirname(__FILE__))
67
+ end
68
+
69
+ # SYM_LINKS methods is used to calculate aliases that will be used for certain entities
70
+ # one of those approaches will be as such
71
+ def self.check_for_sym_link(entries)
72
+ # remove empty strings from array
73
+ entries.reject! { |e| e.empty? }
74
+
75
+ if (entries.size > 0)
76
+ SYM_LINKS.each do |sym_link|
77
+ if entries.first.downcase.to_sym.eql?(sym_link[:alias])
78
+ entries[0] = sym_link[:path].split('/')
79
+ entries.flatten!
80
+ end
81
+ end
82
+ end
83
+
84
+ entries
85
+ end
86
+
87
+ # take current path and see if it is aliased path
88
+ def self.enchance_path_with_alias(path, context_list)
89
+ SYM_LINKS.each do |sym_link|
90
+ if path.downcase.include?(sym_link[:path])
91
+ path = path.gsub(sym_link[:path], sym_link[:alias].to_s)
92
+ end
93
+ end
94
+
95
+ unless context_list.empty?
96
+ init_context = context_list.first.name
97
+ command_clazz = Context.get_command_class(init_context)
98
+ invisible_context = command_clazz.respond_to?(:invisible_context) ? command_clazz.invisible_context() : {}
99
+
100
+ invisible_context.each do |ic|
101
+ path = path.gsub(/\/#{ic}\//,'/')
102
+ end
103
+ end
104
+
105
+ path
106
+ end
107
+
108
+ # Validates and changes context
109
+ def change_context(args, cmd=[])
110
+ begin
111
+ # check if we are doing switch context
112
+ if args.join("").match(/\A\-\Z/)
113
+ if @previous_context
114
+ # swap 2 variables
115
+ @active_context, @previous_context = @previous_context, @active_context
116
+ end
117
+ load_context(active_context.last_context_name)
118
+ return
119
+ end
120
+
121
+ # remember current context
122
+ @previous_context = @active_context.clone_me()
123
+
124
+ # jump to root
125
+ reset if args.join('').match(/^\//)
126
+
127
+ # begin
128
+ # hack: used just to avoid entering assembly/id/node or workspace/node context (remove when include this contexts again)
129
+ first_c, warning_message = nil, nil
130
+ first_c = @active_context.context_list().first.name unless @active_context.context_list().empty?
131
+ tmp_active_context = @active_context.clone_me
132
+ restricted = is_restricted_context(first_c, args, tmp_active_context)
133
+
134
+ args = restricted[:args]
135
+ warning_message = restricted[:message]
136
+ node_specific = restricted[:node_specific]
137
+
138
+ DTK::Client::OsUtil.print(warning_message, :yellow) if warning_message
139
+ # end
140
+
141
+ # Validate and change context
142
+ @active_context, error_message = prepare_context_change(args, @active_context, node_specific, cmd, true)
143
+
144
+ load_context(active_context.last_context_name)
145
+
146
+ raise DTK::Client::DtkValidationError, error_message if error_message
147
+ rescue DTK::Client::DtkValidationError => e
148
+ DTK::Client::OsUtil.print(e.message, :yellow)
149
+ rescue DTK::Shell::Error, Exception => e
150
+ DtkLogger.instance.error_pp(e.message, e.backtrace)
151
+ ensure
152
+ return shell_prompt()
153
+ end
154
+ end
155
+
156
+ # this method is used to scan and provide context to be available Readline.complete_proc
157
+ def dynamic_autocomplete_context(readline_input, line_buffer=[])
158
+ # special case indicator when user starts cc with '/' (go from root)
159
+ goes_from_root = readline_input.start_with?('/')
160
+ # Cloning existing active context, as all changes shouldn't be permanent, but temporary for autocomplete
161
+ active_context_copy = @active_context.clone_me
162
+ # Emptying context copy if it goes from root '/'
163
+ active_context_copy.clear if goes_from_root
164
+ # Invalid context is user leftover to be matched; i.e. 'cc /assembly/te' - 'te' is leftover
165
+ invalid_context = ""
166
+
167
+ # Validate and change context; skip step if user's input is empty or it is equal to '/'
168
+ active_context_copy, error_message, invalid_context = prepare_context_change([readline_input], active_context_copy, nil, line_buffer) unless (readline_input.empty? || readline_input == "/")
169
+
170
+ # using extended_context when we want to use autocomplete from other context
171
+ # e.g. we are in assembly/apache context and want to create-component we will use extended context to add
172
+ # component-templates to autocomplete
173
+ extended_candidates, new_context = {}, nil
174
+ command_clazz = Context.get_command_class(active_context_copy.last_command_name)
175
+
176
+ unless (line_buffer.empty? || line_buffer.strip().empty?)
177
+ line_buffer = line_buffer.split(' ').first
178
+ line_buffer.gsub!('-','_') unless (line_buffer.nil? || line_buffer.empty?)
179
+ end
180
+
181
+ unless command_clazz.nil?
182
+ extended_context = command_clazz.respond_to?(:extended_context) ? command_clazz.extended_context() : {}
183
+
184
+ unless extended_context.empty?
185
+ extended_context = extended_context[:context]
186
+ extended_context.reject!{|k,v| k.to_s!=line_buffer}
187
+
188
+ new_context = extended_context[line_buffer.to_sym] unless line_buffer.nil?
189
+ active_context_copy.push_new_context(new_context, new_context) unless new_context.nil?
190
+ end
191
+ end
192
+
193
+ return get_ac_candidates(active_context_copy, readline_input, invalid_context, goes_from_root, line_buffer)
194
+ end
195
+
196
+ # TODO: this is hack used this to hide 'node' context and use just node_identifier
197
+ # we should rethink the design of shell context if we are about to use different behaviors like this
198
+ def self.check_invisible_context(acc, entries, is_root, line_buffer=[], args=[])
199
+ entries.reject! { |e| e.empty? }
200
+ goes_from_root = args.first.start_with?('/')
201
+
202
+ unless line_buffer.empty?
203
+ command = line_buffer.split(' ').first
204
+ current_c_name = acc.last_command_name
205
+ current_context = acc.last_context
206
+ clazz = DTK::Shell::Context.get_command_class(current_c_name)
207
+ command_from_args = nil
208
+
209
+ if args.first.include?('/')
210
+ command_from_args = goes_from_root ? args.first.split('/')[1] : args.first.split('/').first
211
+ clazz_from_args = DTK::Shell::Context.get_command_class(command_from_args) if command_from_args
212
+ end
213
+
214
+ if (command.eql?('cd') || command.eql?('cc') || command.eql?('popc') || command.eql?('pushc'))
215
+ if is_root
216
+ if entries.size >= 3
217
+ node = entries[2]
218
+ if (node && clazz_from_args.respond_to?(:valid_child?))
219
+ unless clazz_from_args.valid_children().first.to_s.include?(node)
220
+ entries[2] = ["node", node]
221
+ entries.flatten!
222
+ end
223
+ end
224
+ end
225
+ else
226
+ double_dots_count = DTK::Shell::ContextAux.count_double_dots(entries)
227
+
228
+ unless double_dots_count > 0
229
+ if clazz.respond_to?(:invisible_context)
230
+ if current_context.is_command?
231
+ node = entries[1]
232
+ if (node && clazz.respond_to?(:valid_child?))
233
+ unless clazz.valid_children().first.to_s.include?(node)
234
+ entries[1] = ["node", node]
235
+ entries.flatten!
236
+ end
237
+ end
238
+ elsif current_context.is_identifier?
239
+ node = entries[0]
240
+ if (node && clazz.respond_to?(:valid_child?))
241
+ unless clazz.valid_children().first.to_s.include?(node)
242
+ entries[0] = ["node", node]
243
+ entries.flatten!
244
+ end
245
+ end
246
+ end
247
+ end
248
+ end
249
+
250
+ end
251
+ end
252
+
253
+ end
254
+
255
+ entries
256
+ end
257
+
258
+
259
+ def prepare_context_change(args, active_context_copy, node_specific=nil, line_buffer=[], on_complete=false)
260
+ # split original cc command
261
+ entries = args.first.split(/\//)
262
+
263
+ # transform alias to full path
264
+ entries = Context.check_for_sym_link(entries) if root?
265
+ entries = Context.check_invisible_context(active_context_copy, entries, root?, line_buffer, args)
266
+
267
+ # if only '/' or just cc skip validation
268
+ return active_context_copy if entries.empty?
269
+
270
+ current_context_clazz, error_message, current_index = nil, nil, 0
271
+ double_dots_count = DTK::Shell::ContextAux.count_double_dots(entries)
272
+
273
+ # we remove '..' from our entries
274
+ entries = entries.select { |e| !(e.empty? || DTK::Shell::ContextAux.is_double_dot?(e)) }
275
+
276
+ # we go back in context based on '..'
277
+ active_context_copy.pop_context(double_dots_count)
278
+
279
+ # if cd .. back to node, skip node context and go to assembly/workspace context
280
+ if (active_context_copy.last_context && entries)
281
+ active_context_copy.pop_context(1) if (node_specific && active_context_copy.last_context.is_command? && active_context_copy.last_command_name.eql?("node") && on_complete)
282
+ end
283
+
284
+ # special case when using workspace context
285
+ # if do cd .. from workspace/workspace identifier go directly to root not to workspace
286
+ if active_context_copy.name_list.include?("workspace")
287
+ count_workspaces = active_context_copy.name_list.inject(Hash.new(0)) {|h,i| h[i] += 1; h }
288
+ active_context_copy.pop_context(1) if count_workspaces['workspace']==1
289
+ end
290
+
291
+ # we add active commands array to begining, using dup to avoid change by ref.
292
+ context_name_list = active_context_copy.name_list
293
+ entries = context_name_list + entries
294
+
295
+ # we check the size of active commands
296
+ ac_size = context_name_list.size
297
+
298
+ invalid_context = ""
299
+ # check each par for command / value
300
+ (0..(entries.size-1)).step(2) do |i|
301
+ command = entries[i]
302
+ value = entries[i+1]
303
+
304
+ clazz = DTK::Shell::Context.get_command_class(command)
305
+ error_message, invalid_context = validate_command(clazz, current_context_clazz, command, active_context_copy)
306
+
307
+ break if error_message
308
+ # if we are dealing with new entries add them to active_context
309
+ active_context_copy.push_new_context(command, command) if (i >= ac_size)
310
+ current_context_clazz = clazz
311
+
312
+ if value
313
+ # context_hash_data is hash with :name, :identifier values
314
+ context_hash_data, error_message, invalid_context = validate_value(command, value, active_context_copy)
315
+ if error_message
316
+ # hack: used just to avoid entering assembly/id/node or workspace/node context (remove when include this contexts again)
317
+ if ((@active_context.last_context_name.eql?("node") || node_specific) && !@active_context.first_context_name().eql?("node") )
318
+ active_context_copy.pop_context(1)
319
+ end
320
+ break
321
+ end
322
+
323
+ active_context_copy.push_new_context(context_hash_data[:name], command, context_hash_data[:identifier]) if ((i+1) >= ac_size)
324
+ end
325
+ end
326
+
327
+ return active_context_copy, error_message, invalid_context
328
+ end
329
+
330
+ def validate_command(clazz, current_context_clazz, command, active_context_copy=nil)
331
+ error_message = nil
332
+ invalid_context = ""
333
+
334
+ # if command class did not found or if command ends with '-'
335
+ if (clazz.nil? || command.match(/-$/))
336
+ error_message = "Context for '#{command}' could not be loaded.";
337
+ invalid_context = command
338
+ end
339
+
340
+ # check if previous context support this one as a child
341
+ unless current_context_clazz.nil?
342
+ # valid child method is necessery to define parent-child relet.
343
+ if current_context_clazz.respond_to?(:valid_child?)
344
+ root_clazz = DTK::Shell::Context.get_command_class(active_context_copy.first_command_name)
345
+ all_children = root_clazz.all_children() + root_clazz.valid_children()
346
+
347
+ valid_all_children = (root_clazz != current_context_clazz) ? all_children.include?(command.to_sym) : true
348
+ unless (current_context_clazz.valid_child?(command) && valid_all_children)
349
+
350
+ error_message = "'#{command}' context is not valid."
351
+ invalid_context = command
352
+
353
+ if current_context_clazz.respond_to?(:invisible_context)
354
+ ic = current_context_clazz.invisible_context()
355
+ ic.each do |c|
356
+ if c.to_s.include?(command)
357
+ return nil, ""
358
+ end
359
+ end
360
+ end
361
+ end
362
+ else
363
+ error_message = "'#{command}' context is not valid."
364
+ invalid_context = command
365
+ end
366
+ end
367
+
368
+ return error_message, invalid_context
369
+ end
370
+
371
+
372
+ # hack: used just to avoid entering assembly/id/node or workspace/node context (remove when include this contexts again)
373
+ def is_restricted_context(first_c, args = [], tmp_active_context=nil)
374
+ entries = args.first.split(/\//)
375
+ invalid_context = ["workspace/node", "service/node"]
376
+ double_dots_count = DTK::Shell::ContextAux.count_double_dots(entries)
377
+ only_double_dots = entries.select{|e| e.to_s!=".."}||[]
378
+ back_flag = false
379
+
380
+ last_from_current, message = nil, nil
381
+ unless (root? || double_dots_count==0 || !only_double_dots.empty?)
382
+ test_c = @previous_context
383
+ test_c.pop_context(double_dots_count)
384
+ last_from_current = test_c.last_context_name
385
+ back_flag = true
386
+ end
387
+
388
+ unless args.empty?
389
+ first_c ||= entries.first
390
+ last_c = last_from_current||entries.last
391
+
392
+ invalid_context.each do |ac|
393
+ if ac.eql?("#{first_c}/#{last_c}")
394
+ unless last_from_current
395
+ last_1, last_2 = entries.last(2)
396
+ if last_1.eql?(last_2)
397
+ args = entries.join('/')
398
+ return {:args => [args], :node_specific => true}
399
+ end
400
+ end
401
+ message = "'#{last_c}' context is not valid."
402
+ is_valid_id = check_for_id(first_c, last_c, tmp_active_context, args)
403
+
404
+ # if ../ to node context, add one more .. to go to previous context (assembly/id or workspace)
405
+ if back_flag
406
+ message = nil
407
+ entries << ".." if is_valid_id==false
408
+ else
409
+ if is_valid_id==false
410
+ entries.pop
411
+ else
412
+ message = nil
413
+ end
414
+ end
415
+
416
+ args = (entries.size<=1 ? entries : entries.join('/'))
417
+ args = args.is_a?(Array) ? args : [args]
418
+ if args.empty?
419
+ raise DTK::Client::DtkValidationError, message
420
+ else
421
+ return {:args => args, :message => message, :node_specific => true}
422
+ end
423
+
424
+ end
425
+ end
426
+ end
427
+
428
+ return {:args => args, :message => message}
429
+ end
430
+
431
+ def check_for_id(context, command, tmp_active_context, args)
432
+ command_clazz = Context.get_command_class(context)
433
+ invisible_context = command_clazz.respond_to?(:invisible_context) ? command_clazz.invisible_context.map { |e| e.to_s } : []
434
+ entries = args.first.split(/\//)
435
+
436
+ entries = Context.check_for_sym_link(entries) if root?
437
+ unless invisible_context.empty?
438
+ if root?
439
+ tmp_active_context.push_new_context(entries[0], entries[0])
440
+ context_hash_data, error_message, invalid_context = validate_value(entries[0], entries[1], tmp_active_context)
441
+
442
+ return if error_message
443
+ tmp_active_context.push_new_context(context_hash_data[:name], entries[0], context_hash_data[:identifier])
444
+ context_hash_data, error_message, invalid_context = validate_value(command, command, tmp_active_context)
445
+
446
+ return if error_message
447
+ tmp_active_context.push_new_context(context_hash_data[:name], command, context_hash_data[:identifier])
448
+ end
449
+
450
+
451
+ node_ids = get_command_identifiers(invisible_context.first.to_s, tmp_active_context)
452
+ node_names = node_ids ? node_ids.collect { |e| e[:name] } : []
453
+ end
454
+
455
+ return node_names.include?(command)
456
+ end
457
+
458
+
459
+
460
+ def validate_value(command, value, active_context_copy=nil)
461
+ context_hash_data = nil
462
+ invalid_context = ""
463
+ # check value
464
+ if value
465
+ context_hash_data = valid_id?(command, value, nil, active_context_copy)
466
+ unless context_hash_data
467
+ error_message = "Identifier '#{value}' is not valid."
468
+ # error_message = "Identifier '#{value}' for context '#{command}' is not valid";
469
+ invalid_context = value
470
+ end
471
+ end
472
+
473
+ return context_hash_data, error_message, invalid_context
474
+ end
475
+
476
+ # load context will load list of commands available for given command (passed)
477
+ # to method. Context is list of command available at current tier.
478
+ def load_context(command_name=nil)
479
+ # when switching to tier 2 we need to use command name from tier one
480
+ # e.g. cc library/public, we are caching context under library_1, library_2
481
+ # so getting context for 'public' will not work and we use than library
482
+ command_name = root? ? 'dtk' : @active_context.last_command_name
483
+
484
+ # if there is no new context (current) we use old one
485
+ @current = current_context_task_names() || @current
486
+
487
+ client_commands = CLIENT_COMMANDS
488
+ client_commands.concat(DEV_COMMANDS) if DTK::Configuration.get(:development_mode)
489
+
490
+ # we add client commands
491
+ @current.concat(client_commands).sort!
492
+
493
+ # holder for commands to be used since we do not want to remember all of them
494
+ @context_commands = @current
495
+
496
+ # we load thor command class identifiers for autocomplete context list
497
+ command_context = get_command_identifiers(command_name)
498
+
499
+ command_name_list = command_context ? command_context.collect { |e| e[:name] } : []
500
+ @context_commands.concat(command_name_list) if current_command?
501
+
502
+ # logic behind context loading
503
+ #Readline.completer_word_break_characters=" "
504
+ Readline.completion_proc = proc { |input| dynamic_autocomplete_context(input, Readline.respond_to?("line_buffer") ? Readline.line_buffer : [])}
505
+ end
506
+
507
+ def push_context()
508
+ raise "DEV WE need to RE-IMPLEMENT this."
509
+ @current_path = @active_context.full_path()
510
+ @dirs.unshift(@current_path) unless @current_path.nil?
511
+ end
512
+
513
+ # resets context
514
+ def reset
515
+ @active_context.clear
516
+ load_context()
517
+ end
518
+
519
+ # when e.g assembly is deleted we want it to be removed from list without
520
+ # exiting dtk-shell
521
+ def reload_cached_tasks(command_name)
522
+ # we clear @current since this will be reloaded
523
+ @current = nil
524
+
525
+ load_context(command_name)
526
+ end
527
+
528
+ # gets current path for shell
529
+ def shell_prompt
530
+ return root? ? DTK_ROOT_PROMPT : "dtk:#{@active_context.full_path}>"
531
+ end
532
+
533
+ def root_tasks
534
+ return @cached_tasks['dtk']
535
+ end
536
+
537
+ # returns true if context is on root at the moment
538
+ def root?
539
+ return @active_context.empty?
540
+ end
541
+
542
+ def current_command?
543
+ return @active_context.current_command?
544
+ end
545
+
546
+ def current_identifier?
547
+ return @active_context.current_identifier?
548
+ end
549
+
550
+ def current_alt_identifier?
551
+ return @active_context.current_alt_identifier?
552
+ end
553
+
554
+ # returns list of tasks for given command name
555
+ def current_context_task_names()
556
+ @cached_tasks.fetch(@active_context.get_task_cache_id(),[]).dup
557
+ end
558
+
559
+ # checks if method name is valid in current context
560
+ def method_valid?(method_name)
561
+ # validate method, see if we support given method in current tasks
562
+ (current_context_task_names() + ['help']).include?(method_name)
563
+ end
564
+
565
+
566
+ # adds command to current list of active commands
567
+ def push_to_active_context(context_name, entity_name, context_value = nil)
568
+ @active_context.push_new_context(context_name, entity_name, context_value)
569
+ end
570
+
571
+ # remove last active command, and returns it
572
+ def pop_from_active_context
573
+ return @active_context.pop_context
574
+ end
575
+
576
+ # calls 'valid_id?' method in Thor class to validate ID/NAME
577
+ def valid_id?(thor_command_name,value, override_context_params=nil, active_context_copy=nil)
578
+
579
+ command_clazz = Context.get_command_class(thor_command_name)
580
+ if command_clazz.list_method_supported?
581
+ if override_context_params
582
+ context_params = override_context_params
583
+ else
584
+ context_params = get_command_parameters(thor_command_name, [], active_context_copy)[2]
585
+ end
586
+
587
+ tmp = command_clazz.valid_id?(value, @conn, context_params)
588
+ return tmp
589
+ end
590
+
591
+ return nil
592
+ end
593
+
594
+ def get_ac_candidates(active_context_copy, readline_input, invalid_context, goes_from_root, line_buffer=[])
595
+ # helper indicator for case when there are more options in current context and cc command is not ended with '/'
596
+ cutoff_forcely = false
597
+ # input string segment used to filter results candidates
598
+ results_filter = (readline_input.match(/\/$/) && invalid_context.empty?) ? "" : readline_input.split("/").last
599
+ results_filter ||= ""
600
+
601
+ command_clazz = Context.get_command_class(active_context_copy.last_command_name)
602
+ extended_context_commands = nil
603
+
604
+ unless command_clazz.nil?
605
+ extended_context = command_clazz.respond_to?(:extended_context) ? command_clazz.extended_context() : {}
606
+
607
+ unless extended_context.empty?
608
+ extended_context = extended_context[:command]
609
+ extended_context.reject!{|k,v| k.to_s!=line_buffer} if extended_context
610
+ extended_context_commands = extended_context[line_buffer.to_sym] unless (line_buffer.empty? || extended_context.nil?)
611
+ end
612
+ end
613
+
614
+ if extended_context_commands
615
+ context_candidates = load_extended_context_commands(extended_context_commands, active_context_copy)
616
+ else
617
+ # If command does not end with '/' check if there are more than one result candidate for current context
618
+ if !readline_input.empty? && !readline_input.match(/\/$/) && invalid_context.empty? && !active_context_copy.empty?
619
+ context_list = active_context_copy.context_list
620
+ context_name = context_list.size == 1 ? nil : context_list[context_list.size-2] # if case when on 1st level, return root candidates
621
+ context_candidates = get_ac_candidates_for_context(context_name, active_context_copy)
622
+ cutoff_forcely = true
623
+ else
624
+ # If last context is command, load all identifiers, otherwise, load next possible context command; if no contexts, load root tasks
625
+ context_candidates = get_ac_candidates_for_context(active_context_copy.last_context(), active_context_copy)
626
+ end
627
+ end
628
+
629
+ # checking if results will contain context candidates based on input string segment
630
+ context_candidates = context_candidates.grep( /^#{Regexp.escape(results_filter)}/ )
631
+
632
+ # Show all context tasks if active context orignal and it's copy are on same context, and are not on root,
633
+ # and if readline has one split result indicating user is not going trough n-level, but possibly executing a task
634
+ task_candidates = []
635
+
636
+ #task_candidates = @context_commands if (active_context_copy.last_context_name() == @active_context.last_context_name() && !active_context_copy.empty?)
637
+ task_candidates = @context_commands if (active_context_copy.last_context_name() == @active_context.last_context_name() && !active_context_copy.empty? && readline_input.split("/").size <= 1)
638
+
639
+ # create results object filtered by user input segment (results_filter)
640
+ task_candidates = task_candidates.grep( /^#{Regexp.escape(results_filter)}/ )
641
+
642
+ # autocomplete candidates are both context and task candidates; remove duplicates in results
643
+ results = context_candidates
644
+
645
+ # if command is 'cc/cd/pushc' displat only context candidates
646
+ if line_buffer.empty?
647
+ results += task_candidates
648
+ else
649
+ is_cc = line_buffer.split(' ')
650
+ results += task_candidates unless (IDENTIFIERS_ONLY.include?(is_cc.first) || extended_context_commands)
651
+ end
652
+
653
+ # remove duplicate context or task candidates
654
+ results.uniq!
655
+
656
+ # Send system beep if there are no candidates
657
+ if results.empty?
658
+ print "\a"
659
+ return []
660
+ end
661
+
662
+ # default value of input user string
663
+ input_context_path = readline_input
664
+
665
+ # cut off last context if it is leftover (invalid_context),
666
+ # or if last context is not finished with '/' and it can have more than option for current context
667
+ # i.e. dtk> cc assembly - have 2 candidates: 'assembly' and 'assembly-template'
668
+ if !invalid_context.empty? || cutoff_forcely
669
+ start_index = goes_from_root ? 1 : 0 # if it starts with / don't take first element after split
670
+ input_context_path = readline_input.split("/")[start_index.. -2].join("/")
671
+ input_context_path = input_context_path + "/" unless input_context_path.empty?
672
+ input_context_path = "/" + input_context_path if goes_from_root
673
+ end
674
+
675
+ # Augment input string with candidates to satisfy thor
676
+ results = results.map { |element| (input_context_path + element) }
677
+
678
+ # If there is only one candidate, and candidate is not task operation
679
+ #return (results.size() == 1 && !context_candidates.empty?) ? (results.first + "/") : results
680
+ return results
681
+
682
+ end
683
+
684
+ def get_ac_candidates_for_context(context, active_context_copy)
685
+ # If last context is command, load all identifiers, otherwise, load next possible context command; if no contexts, load root tasks
686
+ if context
687
+ if context.is_command?
688
+ command_identifiers = get_command_identifiers(context.name, active_context_copy)
689
+ n_level_ac_candidates = command_identifiers ? command_identifiers.collect { |e| e[:name] } : []
690
+ else
691
+ command_clazz = Context.get_command_class(active_context_copy.last_command_name)
692
+ root_clazz = DTK::Shell::Context.get_command_class(active_context_copy.first_command_name)
693
+ valid_all_children = (root_clazz != command_clazz) ? (root_clazz.all_children() + root_clazz.valid_children()) : []
694
+ n_level_ac_candidates = command_clazz.respond_to?(:valid_children) ? command_clazz.valid_children.map { |e| e.to_s } : []
695
+
696
+ n_level_ac_candidates.keep_if{|v| valid_all_children.include?(v.to_sym)} unless valid_all_children.empty?
697
+ invisible_context = command_clazz.respond_to?(:invisible_context) ? command_clazz.invisible_context.map { |e| e.to_s } : []
698
+
699
+ unless invisible_context.empty?
700
+ node_ids = get_command_identifiers(invisible_context.first.to_s, active_context_copy)
701
+ node_names = node_ids ? node_ids.collect { |e| e[:name] } : []
702
+
703
+ n_level_ac_candidates.concat(node_names)
704
+ end
705
+
706
+ n_level_ac_candidates
707
+ end
708
+ else
709
+ n_level_ac_candidates = ROOT_TASKS
710
+ end
711
+ end
712
+
713
+ # get class identifiers for given thor command, returns array of identifiers
714
+ def get_command_identifiers(thor_command_name, active_context_copy=nil)
715
+ begin
716
+ command_clazz = Context.get_command_class(thor_command_name)
717
+ if command_clazz.list_method_supported?
718
+ # take just hashed arguemnts from multi return method
719
+ hashed_args = get_command_parameters(thor_command_name, [], active_context_copy)[2]
720
+ return command_clazz.get_identifiers(@conn, hashed_args)
721
+ end
722
+ rescue DTK::Client::DtkValidationError => e
723
+ # TODO Check if handling needed. Error should happen only when autocomplete ID search illigal
724
+ end
725
+
726
+ return []
727
+ end
728
+
729
+ def load_extended_context_commands(extended_context_commands, active_context_copy)
730
+ candidates = []
731
+ entity_name = active_context_copy.last_context
732
+
733
+ if entity_name.is_identifier?
734
+ endpoint = extended_context_commands[:endpoint]
735
+ url = extended_context_commands[:url]
736
+ opts = extended_context_commands[:opts]||{}
737
+
738
+ id_label = "#{endpoint}_id".to_sym
739
+ id = entity_name.identifier
740
+ opts[id_label] = id
741
+
742
+ response_ruby_obj = DTK::Client::CommandBaseThor.get_cached_response(endpoint.to_sym, url, opts)
743
+ return [] unless response_ruby_obj.ok?
744
+
745
+ response_ruby_obj.data.each do |d|
746
+ candidates << d["display_name"]
747
+ end
748
+ end
749
+
750
+ candidates
751
+ end
752
+
753
+ # changes command and argument if argument is plural of one of
754
+ # the possible commands on tier1 e.g. libraries, assemblies
755
+ # return 2 values cmd, args
756
+ def reverse_commands(cmd, args)
757
+ # iterates trough current context available commands
758
+ @current.each do |available_commands|
759
+ # singulirazes command, e.g. libraries => library
760
+ command_singular=args.first.singularize
761
+ if available_commands.eql?(command_singular)
762
+ cmd, args = command_singular, [cmd]
763
+ end
764
+ end
765
+
766
+ return cmd, args
767
+ end
768
+
769
+ def get_dtk_command_parameters(entity_name, args)
770
+ method_name, entity_name_id = nil, nil
771
+ context_params = ContextParams.new
772
+
773
+ if (ROOT_TASKS + ['dtk']).include?(entity_name)
774
+ Context.require_command_class(entity_name)
775
+ available_tasks = Context.get_command_class(entity_name).task_names
776
+ if available_tasks.include?(args.first)
777
+ method_name = args.shift
778
+ else
779
+ entity_name_id = args.shift
780
+ method_name = args.shift
781
+ end
782
+ else
783
+ raise DTK::Client::DtkError, "Could not find context \"#{entity_name}\"."
784
+ end
785
+
786
+ # if no method specified use help
787
+ method_name ||= 'help'
788
+
789
+ context_params.add_context_to_params(entity_name, entity_name)
790
+
791
+ if entity_name_id
792
+ identifier_response = valid_id?(entity_name, entity_name_id, context_params)
793
+ if identifier_response
794
+ context_params.add_context_to_params(identifier_response[:name], entity_name, identifier_response[:identifier])
795
+ else
796
+ raise DTK::Client::DtkError, "Could not validate identifier \"#{entity_name_id}\"."
797
+ end
798
+ end
799
+
800
+ # extract thor options
801
+ clazz = Context.get_command_class(entity_name)
802
+ options = Context.get_thor_options(clazz, method_name) unless clazz.nil?
803
+ args, thor_options, invalid_options = Context.parse_thor_options(args, options)
804
+ context_params.method_arguments = args
805
+
806
+
807
+ unless available_tasks.include?(method_name)
808
+ raise DTK::Client::DtkError, "Could not find task \"#{method_name}\"."
809
+ end
810
+
811
+ raise DTK::Client::DtkValidationError, "Option '#{invalid_options.first}' is not valid for current command!" unless invalid_options.empty?
812
+
813
+ return entity_name, method_name, context_params, thor_options
814
+ end
815
+
816
+ #
817
+ # We use enrich data to help when using dynamic_context loading, Readline.completition_proc
818
+ # See bellow for more details
819
+ #
820
+ def get_command_parameters(cmd,args, active_context_copy=nil)
821
+ # To support autocomplete feature, temp active context may be forwarded into method
822
+ active_context_copy = @active_context unless active_context_copy
823
+
824
+ entity_name, method_name, option_types = nil, nil, nil
825
+
826
+ context_params = ContextParams.new
827
+
828
+ if root? && !args.empty?
829
+ # this means that somebody is calling command with assembly/.. method_name
830
+ entity_name = cmd
831
+ method_name = args.shift
832
+ else
833
+ context_params.current_context = active_context_copy.clone_me
834
+ entity_name = active_context_copy.name_list.first
835
+ entity_name ||= "dtk"
836
+ method_name = cmd
837
+ end
838
+
839
+ # extract thor options
840
+ clazz = Context.get_command_class(entity_name)
841
+ current_context_command = active_context_copy.last_command_name
842
+
843
+ if (current_context_command != entity_name)
844
+ current_context_clazz = Context.get_command_class(current_context_command)
845
+ options = Context.get_thor_options(current_context_clazz, cmd) unless current_context_command.nil?
846
+ else
847
+ options = Context.get_thor_options(clazz, cmd) unless clazz.nil?
848
+ end
849
+
850
+ # set rest of arguments as method options
851
+ args, thor_options, invalid_options = Context.parse_thor_options(args, options)
852
+ context_params.method_arguments = args
853
+
854
+ return entity_name, method_name, context_params, thor_options, invalid_options
855
+ end
856
+
857
+ private
858
+
859
+ #
860
+ # method takes parameters that can hold specific thor options
861
+ #
862
+ def self.parse_thor_options(args, options=nil)
863
+ type, invalid_options = nil, []
864
+
865
+ # options to handle thor options -m MESSAGE and --list
866
+ options_param_args = []
867
+ args.each_with_index do |e,i|
868
+ if (e.match(/^\-[a-zA-Z]?/) || e.match(/^\-\-/))
869
+ type = Context.get_option_type(options, e) unless options.nil?
870
+ if type.nil?
871
+ options_param_args = nil
872
+ invalid_options << e
873
+ break
874
+ # raise DTK::Client::DtkValidationError, "Option '#{e}' is not valid for current command!"
875
+ else
876
+ options_param_args << e
877
+ options_param_args << args[i+1] unless type == :boolean
878
+ end
879
+ end
880
+ end
881
+
882
+ # remove thor_options
883
+ args = args - options_param_args unless options_param_args.nil?
884
+
885
+ return args, options_param_args, invalid_options
886
+ end
887
+
888
+ def self.get_thor_options(clazz, command)
889
+ command = command.gsub('-','_')
890
+ options = nil
891
+ options = clazz.all_tasks[command].options.collect{|k,v|{:alias=>v.aliases,:name=>v.name,:type=>v.type,:switch=>v.switch_name}} unless clazz.all_tasks[command].nil?
892
+
893
+ return options
894
+ end
895
+
896
+ def self.get_option_type(options, option)
897
+ @ret = nil
898
+
899
+ options.each do |opt|
900
+ @ret = opt[:type] if(opt[:alias].first == option || opt[:switch] == option)
901
+ end
902
+
903
+ return @ret
904
+ end
905
+
906
+ def self.command_to_id_sym(command_name)
907
+ "#{command_name}_id".gsub(/\-/,'_').to_sym
908
+ end
909
+
910
+ def get_latest_tasks(command_name)
911
+ file_name = command_name.gsub('-','_')
912
+ cached_for_command = Context.get_command_class(file_name).tiered_task_names
913
+
914
+ # gets thor command class and then all the task names for that command
915
+ @cached_tasks.merge!(cached_for_command)
916
+ end
917
+
918
+
919
+ # PART OF THE CODE USED FOR WORKING WITH DTK::Shell HISTORY
920
+ public
921
+
922
+ # this file loads sessions history
923
+ def self.load_session_history()
924
+ unless is_there_history_file()
925
+ puts "[INFO] History file is missing, shell history will be disabled. To enable it create file: '#{HISTORY_LOCATION}'"
926
+ return []
927
+ end
928
+
929
+ content = File.open(HISTORY_LOCATION,'r').read
930
+ return (content.empty? ? [] : JSON.parse(content))
931
+ end
932
+
933
+ def self.save_session_history(array_of_commands)
934
+ return [] unless is_there_history_file()
935
+ # we filter the list to remove neighbour duplicates
936
+ filtered_commands = []
937
+ array_of_commands.each_with_index do |a,i|
938
+ filtered_commands << a if (a != array_of_commands[i+1] && is_allowed_command?(a))
939
+ end
940
+
941
+ # make sure we only save up to 'COMMAND_HISTORY_LIMIT' commands
942
+ if filtered_commands.size > COMMAND_HISTORY_LIMIT
943
+ filtered_commands = filtered_commands[-COMMAND_HISTORY_LIMIT,COMMAND_HISTORY_LIMIT+1]
944
+ end
945
+
946
+ File.open(HISTORY_LOCATION,'w') { |f| f.write(filtered_commands.to_json) }
947
+ end
948
+
949
+ private
950
+
951
+ # list of commands that should be excluded from history
952
+ EXCLUDE_COMMAND_LIST = ['create-provider']
953
+
954
+ def self.is_allowed_command?(full_command_entry)
955
+ found = EXCLUDE_COMMAND_LIST.find { |cmd| full_command_entry.include?(cmd) }
956
+ found.nil?
957
+ end
958
+
959
+ def self.is_there_history_file()
960
+ unless File.exists? HISTORY_LOCATION
961
+ begin
962
+ File.open(HISTORY_LOCATION, 'w') {}
963
+ return true
964
+ rescue
965
+ return false
966
+ end
967
+ #DtkLogger.instance.info "[INFO] Session shell history has been disabled, please create file '#{HISTORY_LOCATION}' to enable it."
968
+ end
969
+ return true
970
+ end
971
+ end
972
+ end
973
+ end
974
+
975
+