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