aspera-cli 4.0.0.pre1

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 (88) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +3592 -0
  3. data/bin/ascli +7 -0
  4. data/bin/asession +89 -0
  5. data/docs/Makefile +59 -0
  6. data/docs/README.erb.md +3012 -0
  7. data/docs/README.md +13 -0
  8. data/docs/diagrams.txt +49 -0
  9. data/docs/secrets.make +38 -0
  10. data/docs/test_env.conf +117 -0
  11. data/docs/transfer_spec.html +99 -0
  12. data/examples/aoc.rb +17 -0
  13. data/examples/proxy.pac +60 -0
  14. data/examples/transfer.rb +115 -0
  15. data/lib/aspera/api_detector.rb +60 -0
  16. data/lib/aspera/ascmd.rb +151 -0
  17. data/lib/aspera/ats_api.rb +43 -0
  18. data/lib/aspera/cli/basic_auth_plugin.rb +38 -0
  19. data/lib/aspera/cli/extended_value.rb +88 -0
  20. data/lib/aspera/cli/formater.rb +238 -0
  21. data/lib/aspera/cli/listener/line_dump.rb +17 -0
  22. data/lib/aspera/cli/listener/logger.rb +20 -0
  23. data/lib/aspera/cli/listener/progress.rb +52 -0
  24. data/lib/aspera/cli/listener/progress_multi.rb +91 -0
  25. data/lib/aspera/cli/main.rb +304 -0
  26. data/lib/aspera/cli/manager.rb +440 -0
  27. data/lib/aspera/cli/plugin.rb +90 -0
  28. data/lib/aspera/cli/plugins/alee.rb +24 -0
  29. data/lib/aspera/cli/plugins/ats.rb +231 -0
  30. data/lib/aspera/cli/plugins/bss.rb +71 -0
  31. data/lib/aspera/cli/plugins/config.rb +806 -0
  32. data/lib/aspera/cli/plugins/console.rb +62 -0
  33. data/lib/aspera/cli/plugins/cos.rb +106 -0
  34. data/lib/aspera/cli/plugins/faspex.rb +377 -0
  35. data/lib/aspera/cli/plugins/faspex5.rb +93 -0
  36. data/lib/aspera/cli/plugins/node.rb +438 -0
  37. data/lib/aspera/cli/plugins/oncloud.rb +937 -0
  38. data/lib/aspera/cli/plugins/orchestrator.rb +169 -0
  39. data/lib/aspera/cli/plugins/preview.rb +464 -0
  40. data/lib/aspera/cli/plugins/server.rb +216 -0
  41. data/lib/aspera/cli/plugins/shares.rb +63 -0
  42. data/lib/aspera/cli/plugins/shares2.rb +114 -0
  43. data/lib/aspera/cli/plugins/sync.rb +65 -0
  44. data/lib/aspera/cli/plugins/xnode.rb +115 -0
  45. data/lib/aspera/cli/transfer_agent.rb +251 -0
  46. data/lib/aspera/cli/version.rb +5 -0
  47. data/lib/aspera/colors.rb +39 -0
  48. data/lib/aspera/command_line_builder.rb +137 -0
  49. data/lib/aspera/fasp/aoc.rb +24 -0
  50. data/lib/aspera/fasp/connect.rb +99 -0
  51. data/lib/aspera/fasp/error.rb +21 -0
  52. data/lib/aspera/fasp/error_info.rb +60 -0
  53. data/lib/aspera/fasp/http_gw.rb +81 -0
  54. data/lib/aspera/fasp/installation.rb +240 -0
  55. data/lib/aspera/fasp/listener.rb +11 -0
  56. data/lib/aspera/fasp/local.rb +377 -0
  57. data/lib/aspera/fasp/manager.rb +69 -0
  58. data/lib/aspera/fasp/node.rb +88 -0
  59. data/lib/aspera/fasp/parameters.rb +235 -0
  60. data/lib/aspera/fasp/resume_policy.rb +76 -0
  61. data/lib/aspera/fasp/uri.rb +51 -0
  62. data/lib/aspera/faspex_gw.rb +196 -0
  63. data/lib/aspera/hash_ext.rb +28 -0
  64. data/lib/aspera/log.rb +80 -0
  65. data/lib/aspera/nagios.rb +71 -0
  66. data/lib/aspera/node.rb +14 -0
  67. data/lib/aspera/oauth.rb +319 -0
  68. data/lib/aspera/on_cloud.rb +421 -0
  69. data/lib/aspera/open_application.rb +72 -0
  70. data/lib/aspera/persistency_action_once.rb +42 -0
  71. data/lib/aspera/persistency_folder.rb +91 -0
  72. data/lib/aspera/preview/file_types.rb +300 -0
  73. data/lib/aspera/preview/generator.rb +258 -0
  74. data/lib/aspera/preview/image_error.png +0 -0
  75. data/lib/aspera/preview/options.rb +35 -0
  76. data/lib/aspera/preview/utils.rb +131 -0
  77. data/lib/aspera/preview/video_error.png +0 -0
  78. data/lib/aspera/proxy_auto_config.erb.js +287 -0
  79. data/lib/aspera/proxy_auto_config.rb +34 -0
  80. data/lib/aspera/rest.rb +296 -0
  81. data/lib/aspera/rest_call_error.rb +13 -0
  82. data/lib/aspera/rest_error_analyzer.rb +98 -0
  83. data/lib/aspera/rest_errors_aspera.rb +58 -0
  84. data/lib/aspera/ssh.rb +53 -0
  85. data/lib/aspera/sync.rb +82 -0
  86. data/lib/aspera/temp_file_manager.rb +37 -0
  87. data/lib/aspera/uri_reader.rb +25 -0
  88. metadata +288 -0
@@ -0,0 +1,115 @@
1
+ require 'aspera/cli/basic_auth_plugin'
2
+ require 'aspera/persistency_action_once'
3
+ require "base64"
4
+
5
+ module Aspera
6
+ module Cli
7
+ module Plugins
8
+ # experiments
9
+ class Xnode < BasicAuthPlugin
10
+ def initialize(env)
11
+ super(env)
12
+ self.options.add_opt_simple(:filter_transfer,"Ruby expression for filter at transfer level (cleanup)")
13
+ self.options.add_opt_simple(:filter_file,"Ruby expression for filter at file level (cleanup)")
14
+ self.options.parse_options!
15
+ end
16
+ # "transfer_filter"=>"t['status'].eql?('completed') and t['start_spec']['remote_user'].eql?('faspex')", :file_filter=>"f['status'].eql?('completed') and 0 != f['size'] and t['start_spec']['direction'].eql?('send')"
17
+
18
+ ACTIONS=[ :postprocess, :cleanup, :forward ]
19
+
20
+ # retrieve tranfer list using API and persistency file
21
+ def self.get_transfers_iteration(api_node,params)
22
+ # array with one element max
23
+ iteration_data=[]
24
+ iteration_persistency=nil
25
+ if self.options.get_option(:once_only,:mandatory)
26
+ iteration_persistency=PersistencyActionOnce.new(
27
+ manager: @agents[:persistency],
28
+ data: iteration_data,
29
+ ids: ['xnode',self.options.get_option(:url,:mandatory),self.options.get_option(:username,:mandatory)])
30
+ end
31
+ iteration_data[0]=process_file_events(iteration_data[0])
32
+ # first time run ? or subsequent run ?
33
+ params[:iteration_token]=iteration_data[0] unless iteration_data[0].nil?
34
+ resp=api_node.read('ops/transfers',params)
35
+ transfers=resp[:data]
36
+ if transfers.is_a?(Array) then
37
+ # 3.7.2, released API
38
+ iteration_data[0]=URI.decode_www_form(URI.parse(resp[:http]['Link'].match(/<([^>]+)>/)[1]).query).to_h['iteration_token']
39
+ else
40
+ # 3.5.2, deprecated API
41
+ iteration_data[0]=transfers['iteration_token']
42
+ transfers=transfers['transfers']
43
+ end
44
+ iteration_persistency.save unless iteration_persistency.nil?
45
+ return transfers
46
+ end
47
+
48
+ def execute_action
49
+ api_node=Rest.new({
50
+ :base_url => self.options.get_option(:url,:mandatory),
51
+ :auth => {
52
+ :type => :basic,
53
+ :username => self.options.get_option(:username,:mandatory),
54
+ :password => self.options.get_option(:password,:mandatory)
55
+ }})
56
+ command=self.options.get_next_command(ACTIONS)
57
+ case command
58
+ when :cleanup
59
+ transfers=self.class.get_transfers_iteration(api_node,{:active_only=>false})
60
+ filter_transfer=self.options.get_option(:filter_transfer,:mandatory)
61
+ filter_file=self.options.get_option(:filter_file,:mandatory)
62
+ Log.log.debug("filter_transfer: #{filter_transfer}")
63
+ Log.log.debug("filter_file: #{filter_file}")
64
+ # build list of files to delete: non zero files, downloads, for specified user
65
+ paths_to_delete=[]
66
+ transfers.each do |t|
67
+ if eval(filter_transfer)
68
+ t['files'].each do |f|
69
+ if eval(filter_file)
70
+ if !paths_to_delete.include?(f['path'])
71
+ paths_to_delete.push(f['path'])
72
+ Log.log.info("to delete: #{f['path']}")
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
78
+ # delete files, if any
79
+ if paths_to_delete.length != 0
80
+ Log.log.info("deletion")
81
+ return self.delete_files(api_node,paths_to_delete,nil)
82
+ else
83
+ Log.log.info("nothing to delete")
84
+ end
85
+ return Main.result_nothing
86
+ when :forward
87
+ # detect transfer sessions since last call
88
+ transfers=self.class.get_transfers_iteration(api_node,{:active_only=>false})
89
+ # build list of all files received in all sessions
90
+ filelist=[]
91
+ transfers.select { |t| t['status'].eql?('completed') and t['start_spec']['direction'].eql?('receive') }.each do |t|
92
+ t['files'].each { |f| filelist.push(f['path']) }
93
+ end
94
+ if filelist.empty?
95
+ Log.log.debug("NO TRANSFER".red)
96
+ return Main.result_nothing
97
+ end
98
+ Log.log.debug("file list=#{filelist}")
99
+ # get download transfer spec on destination node
100
+ transfer_params={ :transfer_requests => [ { :transfer_request => { :paths => filelist.map {|i| {:source=>i} } } } ] }
101
+ send_result=api_node.call({:operation=>'POST',:subpath=>'files/download_setup',:json_params=>transfer_params})
102
+ # only one request, so only one answer
103
+ transfer_spec=send_result[:data]['transfer_specs'].first['transfer_spec']
104
+ # execute transfer
105
+ return Main.result_transfer(self.transfer.start(transfer_spec,{:src=>:node_gen3}))
106
+ when :postprocess
107
+ transfers=self.class.get_transfers_iteration(api_node,{:view=>'summary',:direction=>'receive',:active_only=>false})
108
+ return { :type=>:object_list,:data => transfers }
109
+ end # case command
110
+ raise "ERROR: shall not reach this line"
111
+ end # execute_action
112
+ end # Main
113
+ end # Plugin
114
+ end # Cli
115
+ end # Aspera
@@ -0,0 +1,251 @@
1
+ require 'aspera/fasp/local'
2
+ require 'aspera/fasp/connect'
3
+ require 'aspera/fasp/node'
4
+ require 'aspera/fasp/aoc'
5
+ require 'aspera/fasp/http_gw'
6
+ require 'aspera/cli/listener/logger'
7
+ require 'aspera/cli/listener/progress_multi'
8
+
9
+ module Aspera
10
+ module Cli
11
+ # The Transfer agent is a common interface to start a transfer using
12
+ # one of the supported transfer agents
13
+ # provides CLI options to select one of the transfer agents (fasp client)
14
+ class TransferAgent
15
+ # special value for --sources : read file list from arguments
16
+ FILE_LIST_FROM_ARGS='@args'
17
+ # special value for --sources : read file list from transfer spec (--ts)
18
+ FILE_LIST_FROM_TRANSFER_SPEC='@ts'
19
+ private_constant :FILE_LIST_FROM_ARGS,:FILE_LIST_FROM_TRANSFER_SPEC
20
+ # @param cli_objects external objects: option manager, config file manager
21
+ def initialize(cli_objects)
22
+ @opt_mgr=cli_objects[:options]
23
+ @config=cli_objects[:config]
24
+ # transfer spec overrides provided on command line
25
+ @transfer_spec_cmdline={}
26
+ # the currently selected transfer agent
27
+ @agent=nil
28
+ @progress_listener=Listener::ProgressMulti.new
29
+ # source/destination pair, like "paths" of transfer spec
30
+ @transfer_paths=nil
31
+ @opt_mgr.set_obj_attr(:ts,self,:option_transfer_spec)
32
+ @opt_mgr.add_opt_simple(:ts,"override transfer spec values (Hash, use @json: prefix), current=#{@opt_mgr.get_option(:ts,:optional)}")
33
+ @opt_mgr.add_opt_simple(:local_resume,"set resume policy (Hash, use @json: prefix), current=#{@opt_mgr.get_option(:local_resume,:optional)}")
34
+ @opt_mgr.add_opt_simple(:to_folder,"destination folder for downloaded files")
35
+ @opt_mgr.add_opt_simple(:sources,"list of source files (see doc)")
36
+ @opt_mgr.add_opt_simple(:transfer_info,"additional information for transfer client")
37
+ @opt_mgr.add_opt_list(:src_type,[:list,:pair],"type of file list")
38
+ @opt_mgr.add_opt_list(:transfer,[:direct,:httpgw,:connect,:node,:aoc],"type of transfer")
39
+ @opt_mgr.add_opt_list(:progress,[:none,:native,:multi],"type of progress bar")
40
+ @opt_mgr.set_option(:transfer,:direct)
41
+ @opt_mgr.set_option(:src_type,:list)
42
+ @opt_mgr.set_option(:progress,:native) # use native ascp progress bar as it is more reliable
43
+ @opt_mgr.parse_options!
44
+ end
45
+
46
+ def option_transfer_spec; @transfer_spec_cmdline; end
47
+
48
+ def option_transfer_spec=(value); @transfer_spec_cmdline.merge!(value); end
49
+
50
+ def option_transfer_spec_deep_merge(ts); @transfer_spec_cmdline.deep_merge!(ts); end
51
+
52
+ def set_agent_instance(instance)
53
+ @agent=instance
54
+ @agent.add_listener(Listener::Logger.new)
55
+ # use local progress bar if asked so, or if native and non local ascp (because only local ascp has native progress bar)
56
+ if @opt_mgr.get_option(:progress,:mandatory).eql?(:multi) or
57
+ (@opt_mgr.get_option(:progress,:mandatory).eql?(:native) and !@opt_mgr.get_option(:transfer,:mandatory).eql?(:direct))
58
+ @agent.add_listener(@progress_listener)
59
+ end
60
+ end
61
+
62
+ # analyze options and create new agent if not already created or set
63
+ def set_agent_by_options
64
+ return nil unless @agent.nil?
65
+ agent_type=@opt_mgr.get_option(:transfer,:mandatory)
66
+ case agent_type
67
+ when :direct
68
+ agent_options=@opt_mgr.get_option(:transfer_info,:optional)
69
+ agent_options=agent_options.symbolize_keys if agent_options.is_a?(Hash)
70
+ new_agent=Fasp::Local.new(agent_options)
71
+ new_agent.quiet=false if @opt_mgr.get_option(:progress,:mandatory).eql?(:native)
72
+ when :httpgw
73
+ httpgw_config=@opt_mgr.get_option(:transfer_info,:mandatory)
74
+ new_agent=Fasp::HttpGW.new(httpgw_config)
75
+ when :connect
76
+ new_agent=Fasp::Connect.new
77
+ when :node
78
+ # way for code to setup alternate node api in avance
79
+ # support: @preset:<name>
80
+ # support extended values
81
+ node_config=@opt_mgr.get_option(:transfer_info,:optional)
82
+ # if not specified: use default node
83
+ if node_config.nil?
84
+ param_set_name=@config.get_plugin_default_config_name(:node)
85
+ raise CliBadArgument,"No default node configured, Please specify --#{:transfer_info.to_s.gsub('_','-')}" if param_set_name.nil?
86
+ node_config=@config.preset_by_name(param_set_name)
87
+ end
88
+ Log.log.debug("node=#{node_config}")
89
+ raise CliBadArgument,"the node configuration shall be Hash, not #{node_config.class} (#{node_config}), use either @json:<json> or @preset:<parameter set name>" if !node_config.is_a?(Hash)
90
+ # now check there are required parameters
91
+ sym_config=[:url,:username,:password].inject({}) do |h,param|
92
+ raise CliBadArgument,"missing parameter [#{param}] in node specification: #{node_config}" if !node_config.has_key?(param.to_s)
93
+ h[param]=node_config[param.to_s]
94
+ h
95
+ end
96
+ node_api=Rest.new({
97
+ :base_url => sym_config[:url],
98
+ :auth => {
99
+ :type =>:basic,
100
+ :username => sym_config[:username],
101
+ :password => sym_config[:password]
102
+ }})
103
+ new_agent=Fasp::Node.new(node_api)
104
+ when :aoc
105
+ aoc_config=@opt_mgr.get_option(:transfer_info,:optional)
106
+ if aoc_config.nil?
107
+ param_set_name=@config.get_plugin_default_config_name(:aspera)
108
+ raise CliBadArgument,"No default AoC configured, Please specify --#{:transfer_info.to_s.gsub('_','-')}" if param_set_name.nil?
109
+ aoc_config=@config.preset_by_name(param_set_name)
110
+ end
111
+ Log.log.debug("aoc=#{aoc_config}")
112
+ raise CliBadArgument,"the aoc configuration shall be Hash, not #{aoc_config.class} (#{aoc_config}), refer to manual" if !aoc_config.is_a?(Hash)
113
+ # convert keys from string (config) to symbol (agent)
114
+ aoc_config=aoc_config.symbolize_keys
115
+ # convert auth value from string (config) to symbol (agent)
116
+ aoc_config[:auth]=aoc_config[:auth].to_sym if aoc_config[:auth].is_a?(String)
117
+ # private key could be @file:... in config
118
+ aoc_config[:private_key]=ExtendedValue.instance.evaluate(aoc_config[:private_key])
119
+ new_agent=Fasp::Aoc.new(aoc_config)
120
+ else
121
+ raise "INTERNAL ERROR"
122
+ end
123
+ set_agent_instance(new_agent)
124
+ return nil
125
+ end
126
+
127
+ # return destination folder for transfers
128
+ # sets default if needed
129
+ # param: 'send' or 'receive'
130
+ def destination_folder(direction)
131
+ dest_folder=@opt_mgr.get_option(:to_folder,:optional)
132
+ return dest_folder unless dest_folder.nil?
133
+ dest_folder=@transfer_spec_cmdline['destination_root']
134
+ return dest_folder unless dest_folder.nil?
135
+ # default: / on remote, . on local
136
+ case direction.to_s
137
+ when 'send';dest_folder='/'
138
+ when 'receive';dest_folder='.'
139
+ else raise "wrong direction: #{direction}"
140
+ end
141
+ return dest_folder
142
+ end
143
+
144
+ # This is how the list of files to be transfered is specified
145
+ # get paths suitable for transfer spec from command line
146
+ # @return {:source=>(mandatory), :destination=>(optional)}
147
+ # computation is done only once, cache is kept in @transfer_paths
148
+ def ts_source_paths
149
+ # return cache if set
150
+ return @transfer_paths unless @transfer_paths.nil?
151
+ # start with lower priority : get paths from transfer spec on command line
152
+ @transfer_paths=@transfer_spec_cmdline['paths'] if @transfer_spec_cmdline.has_key?('paths')
153
+ # is there a source list option ?
154
+ file_list=@opt_mgr.get_option(:sources,:optional)
155
+ case file_list
156
+ when nil,FILE_LIST_FROM_ARGS
157
+ Log.log.debug("getting file list as parameters")
158
+ # get remaining arguments
159
+ file_list=@opt_mgr.get_next_argument("source file list",:multiple)
160
+ raise CliBadArgument,"specify at least one file on command line or use --sources=#{FILE_LIST_FROM_TRANSFER_SPEC} to use transfer spec" if !file_list.is_a?(Array) or file_list.empty?
161
+ when FILE_LIST_FROM_TRANSFER_SPEC
162
+ Log.log.debug("assume list provided in transfer spec")
163
+ raise CliBadArgument,"transfer spec on command line must have sources" if @transfer_paths.nil?
164
+ # here we assume check of sources is made in transfer agent
165
+ return @transfer_paths
166
+ when Array
167
+ Log.log.debug("getting file list as extended value")
168
+ raise CliBadArgument,"sources must be a Array of String" if !file_list.select{|f|!f.is_a?(String)}.empty?
169
+ else
170
+ raise CliBadArgument,"sources must be a Array, not #{file_list.class}"
171
+ end
172
+ # here, file_list is an Array or String
173
+ if !@transfer_paths.nil?
174
+ Log.log.warn("--sources overrides paths from --ts")
175
+ end
176
+ case @opt_mgr.get_option(:src_type,:mandatory)
177
+ when :list
178
+ # when providing a list, just specify source
179
+ @transfer_paths=file_list.map{|i|{'source'=>i}}
180
+ when :pair
181
+ raise CliBadArgument,"whe using pair, provide even number of paths: #{file_list.length}" unless file_list.length.even?
182
+ @transfer_paths=file_list.each_slice(2).to_a.map{|s,d|{'source'=>s,'destination'=>d}}
183
+ else raise "ERROR"
184
+ end
185
+ Log.log.debug("paths=#{@transfer_paths}")
186
+ return @transfer_paths
187
+ end
188
+
189
+ # start a transfer and wait for completion, plugins shall use this method
190
+ # @param transfer_spec
191
+ # @param options specific options for the transfer_agent
192
+ # options[:src] specifies how destination_root is set (how transfer spec was generated)
193
+ # other options are carried to specific agent
194
+ def start(transfer_spec,options)
195
+ # check parameters
196
+ raise "transfer_spec must be hash" unless transfer_spec.is_a?(Hash)
197
+ raise "options must be hash" unless options.is_a?(Hash)
198
+ # process :src option
199
+ case transfer_spec['direction']
200
+ when 'receive'
201
+ # init default if required in any case
202
+ @transfer_spec_cmdline['destination_root']||=destination_folder(transfer_spec['direction'])
203
+ when 'send'
204
+ case options[:src]
205
+ when :direct
206
+ # init default if required
207
+ @transfer_spec_cmdline['destination_root']||=destination_folder(transfer_spec['direction'])
208
+ when :node_gen3
209
+ # in that case, destination is set in return by application (API/upload_setup)
210
+ # but to_folder was used in initial API call
211
+ @transfer_spec_cmdline.delete('destination_root')
212
+ when :node_gen4
213
+ @transfer_spec_cmdline.delete('destination_root') if @transfer_spec_cmdline.has_key?('destination_root_id')
214
+ else
215
+ raise StandardError,"InternalError: unsupported value: #{options[:src]}"
216
+ end
217
+ end
218
+
219
+ # only used here
220
+ options.delete(:src)
221
+
222
+ # update command line paths, unless destination already has one
223
+ @transfer_spec_cmdline['paths']=transfer_spec['paths'] || ts_source_paths
224
+
225
+ transfer_spec.merge!(@transfer_spec_cmdline)
226
+ # create transfer agent
227
+ self.set_agent_by_options
228
+ Log.log.debug("transfer agent is a #{@agent.class}")
229
+ @agent.start_transfer(transfer_spec,options)
230
+ result=@agent.wait_for_transfers_completion
231
+ @progress_listener.reset
232
+ Fasp::Manager.validate_status_list(result)
233
+ return result
234
+ end
235
+
236
+ # @return :success if all sessions statuses returned by "start" are success
237
+ # else return the first error exception object
238
+ def self.session_status(statuses)
239
+ error_statuses=statuses.select{|i|!i.eql?(:success)}
240
+ return :success if error_statuses.empty?
241
+ return error_statuses.first
242
+ end
243
+
244
+ # shut down if agent requires it
245
+ def shutdown
246
+ @agent.shutdown if @agent.respond_to?(:shutdown)
247
+ end
248
+
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,5 @@
1
+ module Aspera
2
+ module Cli
3
+ VERSION = "4.0.0.pre1"
4
+ end
5
+ end
@@ -0,0 +1,39 @@
1
+ # simple vt100 colors
2
+ class String
3
+ private
4
+ def self.vtcmd(code);"\e[#{code}m";end
5
+ # see https://en.wikipedia.org/wiki/ANSI_escape_code
6
+ # symbol is the method name added to String
7
+ # it adds control chars to set color (and reset at the end).
8
+ VTSTYLES = {
9
+ :bold=>1,
10
+ :italic=>3,
11
+ :underline=>4,
12
+ :blink=>5,
13
+ :reverse_color=>7,
14
+ :black=>30,
15
+ :red=>31,
16
+ :green=>32,
17
+ :brown=>33,
18
+ :blue=>34,
19
+ :magenta=>35,
20
+ :cyan=>36,
21
+ :gray=>37,
22
+ :bg_black=>40,
23
+ :bg_red=>41,
24
+ :bg_green=>42,
25
+ :bg_brown=>43,
26
+ :bg_blue=>44,
27
+ :bg_magenta=>45,
28
+ :bg_cyan=>46,
29
+ :bg_gray=>47,
30
+ }
31
+ private_constant :VTSTYLES
32
+ # defines methods to String, one per entry in VTSTYLES
33
+ VTSTYLES.each do |name,code|
34
+ begin_seq=vtcmd(code)
35
+ end_seq=vtcmd((code >= 10) ? 0 : code+20+(code.eql?(1)?1:0))
36
+ define_method(name){"#{begin_seq}#{self}#{end_seq}"}
37
+ public name
38
+ end
39
+ end
@@ -0,0 +1,137 @@
1
+ module Aspera
2
+ # helper class to build command line from a parameter list (key-value hash)
3
+ # constructor takes hash: { 'param1':'value1', ...}
4
+ # process_param is called repeatedly with all known parameters
5
+ # add_env_args is called to get resulting param list and env var (also checks that all params were used)
6
+ class CommandLineBuilder
7
+
8
+ private
9
+ # default value for command line based on option name
10
+ def switch_name(param_name,options)
11
+ return options[:option_switch] if options.has_key?(:option_switch)
12
+ return '--'+param_name.to_s.gsub('_','-')
13
+ end
14
+
15
+ def env_name(param_name,options)
16
+ return options[:variable]
17
+ end
18
+
19
+ public
20
+
21
+ BOOLEAN_CLASSES=[TrueClass,FalseClass]
22
+
23
+ # @param param_hash
24
+ def initialize(param_hash,params_definition)
25
+ @param_hash=param_hash # keep reference so that it can be modified by caller before calling `process_params`
26
+ @params_definition=params_definition
27
+ @result_env={}
28
+ @result_args=[]
29
+ @used_param_names=[]
30
+ end
31
+
32
+ def warn_unrecognized_params
33
+ # warn about non translated arguments
34
+ @param_hash.each_pair{|key,val|Log.log.warn("unrecognized parameter: #{key} = \"#{val}\"") if !@used_param_names.include?(key)}
35
+ end
36
+
37
+ # adds keys :env :args with resulting values after processing
38
+ # warns if some parameters were not used
39
+ def add_env_args(env,args)
40
+ Log.log.debug("ENV=#{@result_env}, ARGS=#{@result_args}")
41
+ warn_unrecognized_params
42
+ env.merge!(@result_env)
43
+ args.push(*@result_args)
44
+ return nil
45
+ end
46
+
47
+ # transform yes/no to trye/false
48
+ def self.yes_to_true(value)
49
+ case value
50
+ when 'yes'; return true
51
+ when 'no'; return false
52
+ end
53
+ raise "unsupported value: #{value}"
54
+ end
55
+
56
+ # add options directly to ascp command line
57
+ def add_command_line_options(options)
58
+ return if options.nil?
59
+ options.each{|o|@result_args.push(o.to_s)}
60
+ end
61
+
62
+ def process_params
63
+ @params_definition.keys.each do |k|
64
+ process_param(k)
65
+ end
66
+ end
67
+
68
+ # Process a parameter from transfer specification and generate command line param or env var
69
+ # @param param_name : key in transfer spec
70
+ # @param action : type of processing: ignore getvalue envvar opt_without_arg opt_with_arg defer
71
+ # @param options : options for type
72
+ def process_param(param_name,action=nil)
73
+ options=@params_definition[param_name]
74
+ action=options[:type] if action.nil?
75
+ # should not happen
76
+ raise "Internal error: ask processing of param #{param_name}" if options.nil?
77
+ # by default : not mandatory
78
+ options[:mandatory]||=false
79
+ if options.has_key?(:accepted_types)
80
+ # single type is placed in array
81
+ options[:accepted_types]=[options[:accepted_types]] unless options[:accepted_types].is_a?(Array)
82
+ else
83
+ # by default : string, unless it's without arg
84
+ options[:accepted_types]=action.eql?(:opt_without_arg) ? BOOLEAN_CLASSES : [String]
85
+ end
86
+ # check mandatory parameter (nil is valid value)
87
+ raise Fasp::Error.new("mandatory parameter: #{param_name}") if options[:mandatory] and !@param_hash.has_key?(param_name)
88
+ parameter_value=@param_hash[param_name]
89
+ parameter_value=options[:default] if parameter_value.nil? and options.has_key?(:default)
90
+ # check provided type
91
+ raise Fasp::Error.new("#{param_name} is : #{parameter_value.class} (#{parameter_value}), shall be #{options[:accepted_types]}, ") unless parameter_value.nil? or options[:accepted_types].inject(false){|m,v|m or parameter_value.is_a?(v)}
92
+ @used_param_names.push(param_name) unless action.eql?(:defer)
93
+
94
+ # process only non-nil values
95
+ return nil if parameter_value.nil?
96
+
97
+ if options.has_key?(:translate_values)
98
+ # translate using conversion table
99
+ new_value=options[:translate_values][parameter_value]
100
+ raise "unsupported value: #{parameter_value}" if new_value.nil?
101
+ parameter_value=new_value
102
+ end
103
+ raise "unsupported value: #{parameter_value}" unless options[:accepted_values].nil? or options[:accepted_values].include?(parameter_value)
104
+ if options[:encode]
105
+ newvalue=options[:encode].call(parameter_value)
106
+ raise Fasp::Error.new("unsupported #{param_name}: #{parameter_value}") if newvalue.nil?
107
+ parameter_value=newvalue
108
+ end
109
+
110
+ case action
111
+ when :ignore,:defer # ignore this parameter or process later
112
+ return
113
+ when :get_value # just get value
114
+ return parameter_value
115
+ when :envvar # set in env var
116
+ # define ascp parameter in env var from transfer spec
117
+ @result_env[env_name(param_name,options)] = parameter_value
118
+ when :opt_without_arg # if present and true : just add option without value
119
+ add_param=false
120
+ case parameter_value
121
+ when false# nothing to put on command line, no creation by default
122
+ when true; add_param=true
123
+ else raise Fasp::Error.new("unsupported #{param_name}: #{parameter_value}")
124
+ end
125
+ add_param=!add_param if options[:add_on_false]
126
+ add_command_line_options([switch_name(param_name,options)]) if add_param
127
+ when :opt_with_arg # transform into command line option with value
128
+ #parameter_value=parameter_value.to_s if parameter_value.is_a?(Integer)
129
+ parameter_value=[parameter_value] unless parameter_value.is_a?(Array)
130
+ # if transfer_spec value is an array, applies option many times
131
+ parameter_value.each{|v|add_command_line_options([switch_name(param_name,options),v])}
132
+ else
133
+ raise "Error"
134
+ end
135
+ end
136
+ end
137
+ end