a4tools 1.2.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.bundle/install.log +38 -0
- data/.gitignore +2 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +38 -0
- data/a4tools.gemspec +38 -0
- data/bin/deploy_latest_clients +32 -0
- data/bin/devsite_config_server +48 -0
- data/bin/netshell +23 -0
- data/bin/update_server +101 -0
- data/bin/usher +54 -0
- data/lib/a4tools.rb +61 -0
- data/lib/a4tools/version.rb +3 -0
- data/lib/acres_client.rb +376 -0
- data/lib/clients/caching_client.rb +151 -0
- data/lib/clients/deployment_client.rb +53 -0
- data/lib/clients/kai_config_client.rb +39 -0
- data/lib/clients/usher_client.rb +72 -0
- data/lib/clients/usher_mgmt_client.rb +201 -0
- data/lib/event_manager.rb +24 -0
- data/lib/events.json +1 -0
- data/lib/net_shell/builtin_command.rb +312 -0
- data/lib/net_shell/builtin_commands/build.rb +251 -0
- data/lib/net_shell/builtin_commands/cd.rb +12 -0
- data/lib/net_shell/builtin_commands/connect.rb +122 -0
- data/lib/net_shell/builtin_commands/deploy.rb +280 -0
- data/lib/net_shell/builtin_commands/disconnect.rb +15 -0
- data/lib/net_shell/builtin_commands/excerpt.rb +97 -0
- data/lib/net_shell/builtin_commands/exit.rb +7 -0
- data/lib/net_shell/builtin_commands/get.rb +38 -0
- data/lib/net_shell/builtin_commands/help.rb +40 -0
- data/lib/net_shell/builtin_commands/host.rb +126 -0
- data/lib/net_shell/builtin_commands/inject.rb +42 -0
- data/lib/net_shell/builtin_commands/jsoncache.rb +80 -0
- data/lib/net_shell/builtin_commands/kai_event.rb +151 -0
- data/lib/net_shell/builtin_commands/persist.rb +24 -0
- data/lib/net_shell/builtin_commands/pwd.rb +6 -0
- data/lib/net_shell/builtin_commands/recap.rb +188 -0
- data/lib/net_shell/builtin_commands/references.rb +63 -0
- data/lib/net_shell/builtin_commands/select.rb +36 -0
- data/lib/net_shell/builtin_commands/send.rb +74 -0
- data/lib/net_shell/builtin_commands/set.rb +29 -0
- data/lib/net_shell/builtin_commands/show.rb +183 -0
- data/lib/net_shell/builtin_commands/site.rb +122 -0
- data/lib/net_shell/builtin_commands/ssh.rb +62 -0
- data/lib/net_shell/builtin_commands/talk.rb +90 -0
- data/lib/net_shell/builtin_commands/translate.rb +45 -0
- data/lib/net_shell/builtin_commands/unset.rb +14 -0
- data/lib/net_shell/builtin_commands/usher.rb +55 -0
- data/lib/net_shell/builtin_commands/usher_device.rb +39 -0
- data/lib/net_shell/builtin_commands/usher_site.rb +245 -0
- data/lib/net_shell/builtin_commands/usherm_connect.rb +21 -0
- data/lib/net_shell/colors.rb +149 -0
- data/lib/net_shell/command.rb +97 -0
- data/lib/net_shell/io.rb +132 -0
- data/lib/net_shell/net_shell.rb +396 -0
- data/lib/net_shell/prompt.rb +335 -0
- data/lib/object_builder/definitions/app_info_for_script.rb +83 -0
- data/lib/object_builder/definitions/connection_request.rb +28 -0
- data/lib/object_builder/definitions/device_info_for_system.rb +37 -0
- data/lib/object_builder/object_builder.rb +145 -0
- data/lib/talk.json +1 -0
- data/lib/talk_consumer.rb +235 -0
- metadata +279 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
category "Kai"
|
2
|
+
description "Generate machine events"
|
3
|
+
usage "[event_code machine_location]"
|
4
|
+
|
5
|
+
tab do
|
6
|
+
events.events.map { |event| event[:key] }
|
7
|
+
end
|
8
|
+
|
9
|
+
tab :machine do
|
10
|
+
machine_list
|
11
|
+
end
|
12
|
+
|
13
|
+
opt :amount, "Cash value associated with event. Used for jackpot and handpays.", :type => :integer
|
14
|
+
opt :tier, "Player tier level name", :type => :string
|
15
|
+
opt :first_name, "Player first name", :type => :string
|
16
|
+
opt :last_name, "Player last name", :type => :string
|
17
|
+
opt :employee, "Employee card ID", :type => :integer
|
18
|
+
opt :patron, "Patron card I", :type => :integer
|
19
|
+
opt :machine, "Machine location", :type => :string
|
20
|
+
|
21
|
+
opt :allow_duplicate, "Allow duplicate machines when spamming with random machine IDs"
|
22
|
+
opt :spam, "Spam the event repeatedly", :type => :integer, :default => 1
|
23
|
+
opt :spam_interval, "Specify number of seconds between spams in milliseconds", :type => :integer, :default => 1000
|
24
|
+
|
25
|
+
opt :list_names, "List Kai events, alphabetized by name"
|
26
|
+
|
27
|
+
run do
|
28
|
+
if args.length == 1 then
|
29
|
+
list = events.events
|
30
|
+
(list.sort! { |a,b| a[:key] <=> b[:key] }) if params[:list_names]
|
31
|
+
(events.events.map do |event|
|
32
|
+
code = sprintf("%5d", event[:code]).style(:event_code)
|
33
|
+
key = sprintf("%35s", event[:key].to_s).style(:event_key)
|
34
|
+
|
35
|
+
"#{code} #{key} -- #{event[:description]}"
|
36
|
+
end).join("\n")
|
37
|
+
else
|
38
|
+
machine = args[2] rescue random_machine
|
39
|
+
code = event_code(args[1])
|
40
|
+
return show_error("Unknown event code #{code}") if code.nil?
|
41
|
+
return show_error("Unable to connect QA test interface") unless qa.connect_if_needed
|
42
|
+
return show_error("Only have #{machine_list.length} machines to choose from; either reduce spam count or allow duplicates") if params[:spam] > machine_list.length and not params[:allow_duplicate]
|
43
|
+
|
44
|
+
index = 0
|
45
|
+
allowed_list = machine_list
|
46
|
+
params[:spam].times do
|
47
|
+
index = index + 1
|
48
|
+
machine = params[:machine] || allowed_list.sample
|
49
|
+
allowed_list.delete(machine) unless params[:allow_duplicate]
|
50
|
+
|
51
|
+
message = build_request(args[1..-2].sample, machine)
|
52
|
+
puts "Sending event #{index} of #{params[:spam]}: #{event_description(message)}"
|
53
|
+
send_event(message)
|
54
|
+
sleep params[:spam_interval] * 1.0/1000.0 unless index == params[:spam]
|
55
|
+
end
|
56
|
+
|
57
|
+
""
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
|
63
|
+
def event_description(event)
|
64
|
+
name = event_name(event[:eventList][0][:event][:idEventCode]) || event[:eventList][0][:event][:idEventCode]
|
65
|
+
location = event[:eventList][0][:location]
|
66
|
+
"#{name} @ #{location}"
|
67
|
+
end
|
68
|
+
|
69
|
+
def machine_list
|
70
|
+
@shell.client(:config).machines rescue []
|
71
|
+
end
|
72
|
+
|
73
|
+
def random_machine
|
74
|
+
machine_list.sample
|
75
|
+
end
|
76
|
+
|
77
|
+
def send_event(message)
|
78
|
+
qa.inject_token(message)
|
79
|
+
jsonrpc = qa.jsonrpc_message(:qaTestEvent, "qatest", nil, message)
|
80
|
+
qa.send_message(jsonrpc).body
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_request(code, machine)
|
84
|
+
{
|
85
|
+
__class: "com.acres4.common.info.mercury.qatest.QATestEventRequest",
|
86
|
+
tok:nil,
|
87
|
+
eventList:[ qa_event_table_item(code, machine) ]
|
88
|
+
}
|
89
|
+
end
|
90
|
+
|
91
|
+
def qa_event_table_item(code, machine)
|
92
|
+
{
|
93
|
+
event:event_table_item(code),
|
94
|
+
location:machine
|
95
|
+
}
|
96
|
+
end
|
97
|
+
|
98
|
+
def event_table_item(code)
|
99
|
+
{
|
100
|
+
idEventCode:code,
|
101
|
+
hostEvent:host_event(code),
|
102
|
+
collectStats:false,
|
103
|
+
receiptTime:Time.now.to_i,
|
104
|
+
playerRating:player_rating
|
105
|
+
}
|
106
|
+
end
|
107
|
+
|
108
|
+
def host_event(code)
|
109
|
+
{
|
110
|
+
priKey:0,
|
111
|
+
hostDeviceId:nil,
|
112
|
+
eventCode:code.to_s,
|
113
|
+
idPatron:params[:patron] || 0,
|
114
|
+
idEmployee:params[:employee] || 0,
|
115
|
+
amount:params[:amount] || 0,
|
116
|
+
meters:nil,
|
117
|
+
eventTime:Time.now.to_i,
|
118
|
+
hostTime:Time.now.to_i
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
def player_rating
|
123
|
+
return nil unless params[:patron] or params[:firstName] or params[:lastName] or params[:loyaltyTierStr]
|
124
|
+
|
125
|
+
{
|
126
|
+
idPatron:params[:patron] || 0,
|
127
|
+
firstName:params[:first_name] || "Little",
|
128
|
+
lastName:params[:last_name] || "Timmy",
|
129
|
+
loyaltyTierStr:params[:tier] || "Wood"
|
130
|
+
}
|
131
|
+
end
|
132
|
+
|
133
|
+
def event_by_identifier(identifier)
|
134
|
+
identifier ||= args[1]
|
135
|
+
identifier = identifier.to_i if identifier.is_a? String and identifier.match(/^[0-9]+$/)
|
136
|
+
events.events.each do |event|
|
137
|
+
return event if(event[:key].to_s == identifier.to_s or event[:code] == identifier)
|
138
|
+
end
|
139
|
+
|
140
|
+
pp identifier
|
141
|
+
pp args[1]
|
142
|
+
nil
|
143
|
+
end
|
144
|
+
|
145
|
+
def event_code(identifier=nil)
|
146
|
+
event_by_identifier(identifier)[:code]
|
147
|
+
end
|
148
|
+
|
149
|
+
def event_name(identifier=nil)
|
150
|
+
event_by_identifier(identifier)[:key]
|
151
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
category "Shell management"
|
2
|
+
description "Persist an environment variable"
|
3
|
+
usage "variable1 variable2 ..."
|
4
|
+
|
5
|
+
help "Causes an evironment variable to persist between netshell sessions."
|
6
|
+
|
7
|
+
tab do
|
8
|
+
@shell.env.keys.reject { |key| args.include? key.to_s }
|
9
|
+
end
|
10
|
+
|
11
|
+
validate { (args.length >= 2 and not params[:all]) or (args.length == 1 and params[:all]) }
|
12
|
+
|
13
|
+
opt :all, "Persist all variables"
|
14
|
+
|
15
|
+
run do
|
16
|
+
variables = if params[:all]
|
17
|
+
[] # causes persist to save everything
|
18
|
+
else
|
19
|
+
args[1..-1]
|
20
|
+
end
|
21
|
+
|
22
|
+
@shell.persist_env(variables)
|
23
|
+
""
|
24
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
category "Protocol manipulation"
|
2
|
+
description "Print message history for current connection"
|
3
|
+
usage [
|
4
|
+
"#{command} show all messages",
|
5
|
+
"#{command} n show nth message before most recent",
|
6
|
+
"#{command} start end show messages from start to end"
|
7
|
+
]
|
8
|
+
|
9
|
+
help <<-EOS
|
10
|
+
Examples:
|
11
|
+
List all messages
|
12
|
+
#{command}
|
13
|
+
|
14
|
+
List most recent message
|
15
|
+
#{command} 0
|
16
|
+
|
17
|
+
List most recent 3 messages
|
18
|
+
#{command} 2 0
|
19
|
+
|
20
|
+
List first message in conversation
|
21
|
+
#{command} -1
|
22
|
+
|
23
|
+
List first 5 messages in conversation
|
24
|
+
#{command} -5 -1
|
25
|
+
EOS
|
26
|
+
|
27
|
+
opt :client, "Recap this history for a client other than the active client", :type => :string
|
28
|
+
opt :summary, "Only list class names in text mode"
|
29
|
+
opt :json, "Output as JSON"
|
30
|
+
opt :raw, "Don't wrap JSON objects with metadata objects."
|
31
|
+
opt :array, "Force JSON to always be an array, even if only a single object is returned"
|
32
|
+
opt :reverse, "Index messages from end of conversation (so message 0 is most recent)"
|
33
|
+
|
34
|
+
validate do
|
35
|
+
return "Must have an active client" if client.nil? and not params[:client]
|
36
|
+
return "#{params[:client]}: unknown client" if params[:client] and not @shell.clients.include? params[:client].to_sym
|
37
|
+
true
|
38
|
+
end
|
39
|
+
|
40
|
+
tab :client do
|
41
|
+
@shell.clients
|
42
|
+
end
|
43
|
+
|
44
|
+
run { use_json? ? json_output : text_output }
|
45
|
+
|
46
|
+
###
|
47
|
+
|
48
|
+
def target_client
|
49
|
+
params[:client] ? @shell.client(params[:client]) : client
|
50
|
+
end
|
51
|
+
|
52
|
+
def messages
|
53
|
+
messages = target_client.history
|
54
|
+
end
|
55
|
+
|
56
|
+
def range_for_args(args)
|
57
|
+
case args.length
|
58
|
+
when 0,1
|
59
|
+
range = (0 .. -1)
|
60
|
+
when 2
|
61
|
+
range = args[1].to_i .. args[1].to_i
|
62
|
+
when 3
|
63
|
+
low = [args[1], args[2]].min
|
64
|
+
high = [args[1], args[2]].max
|
65
|
+
range = low.to_i .. high.to_i
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def sender(msg)
|
70
|
+
case msg[:sender]
|
71
|
+
when :client
|
72
|
+
"client".light_blue
|
73
|
+
when :server
|
74
|
+
"server".magenta
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def timecode(msg)
|
79
|
+
delta = msg[:time] - target_client.start_time
|
80
|
+
all_seconds = delta.to_i
|
81
|
+
|
82
|
+
milliseconds = 1000.0*(delta - all_seconds)
|
83
|
+
hours = (all_seconds / 3660).to_i
|
84
|
+
minutes = ((all_seconds - 3660*hours) / 60).to_i
|
85
|
+
seconds = (all_seconds - 3600*hours - 60*minutes).to_i
|
86
|
+
|
87
|
+
return sprintf("%3d:%02d:%02d.%03d", hours, minutes, seconds, milliseconds).light_black
|
88
|
+
end
|
89
|
+
|
90
|
+
def text_output
|
91
|
+
index = -1
|
92
|
+
set = params[:reverse] ? messages : messages.reverse
|
93
|
+
(set.reverse[range_for_args(args)].map do |msg|
|
94
|
+
index += 1
|
95
|
+
index_str = sprintf("%3d", index)
|
96
|
+
"#{index_str} #{timecode(msg)} #{sender(msg)} #{render_text(msg[:message])}"
|
97
|
+
end).join("\n")
|
98
|
+
end
|
99
|
+
|
100
|
+
def json_output
|
101
|
+
set = params[:reverse] ? messages : messages.reverse
|
102
|
+
set = (set.reverse[range_for_args(args)].map do |msg|
|
103
|
+
if params[:raw] then
|
104
|
+
msg[:message]
|
105
|
+
else
|
106
|
+
msg.reject { |k,v| k == :raw }
|
107
|
+
end
|
108
|
+
end)
|
109
|
+
|
110
|
+
set = set[0] if set.length == 1 and not params[:array]
|
111
|
+
set.to_json
|
112
|
+
end
|
113
|
+
|
114
|
+
def use_json?
|
115
|
+
params[:json] or params[:raw] or params[:array]
|
116
|
+
end
|
117
|
+
|
118
|
+
def render_text(message)
|
119
|
+
if params[:summary] then
|
120
|
+
render_summary(message)
|
121
|
+
else
|
122
|
+
message.is_a?(Hash) ? JSON.generate(message) : message
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def render_summary(message)
|
127
|
+
toplevel, cls = pick_toplevel(message)
|
128
|
+
puts toplevel if cls.nil?
|
129
|
+
cls ||= talk.guess_class(toplevel)
|
130
|
+
class_name = cls.style(:class) || "Unknown"
|
131
|
+
id_str = message[:id] ? sprintf("%3d", message[:id]) : " "
|
132
|
+
|
133
|
+
sprintf("%s %20s %s %s",
|
134
|
+
id_str.style(:id),
|
135
|
+
method(message).style(:method_name),
|
136
|
+
class_name.style(:class),
|
137
|
+
human_readable_size(message.to_json.length)
|
138
|
+
)
|
139
|
+
end
|
140
|
+
|
141
|
+
def human_readable_size(size)
|
142
|
+
titles = [ "B", "KiB", "MiB", "GiB", "TiB" ]
|
143
|
+
titles.each_index do |idx|
|
144
|
+
title = titles[idx]
|
145
|
+
unit = 1024**idx
|
146
|
+
boundary = 1024*unit
|
147
|
+
return "#{size/unit} #{title}" if size < boundary or title == titles.last
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def method(message)
|
152
|
+
return message[:method] if message.has_key? :method
|
153
|
+
request = find_request_with_id(message[:id])
|
154
|
+
request[:method] rescue "unknown"
|
155
|
+
end
|
156
|
+
|
157
|
+
def find_request_with_id(id)
|
158
|
+
target_client.history.each do |msg|
|
159
|
+
next if msg[:message].nil? or msg[:message][:id].nil? or not(msg[:message].has_key? :params)
|
160
|
+
return msg[:message] if msg[:message][:id].to_i == id.to_i
|
161
|
+
end
|
162
|
+
|
163
|
+
nil
|
164
|
+
end
|
165
|
+
|
166
|
+
def pick_toplevel(message)
|
167
|
+
message = symbolify(JSON.parse(message)) if message.is_a? String
|
168
|
+
if message[:jsonrpc] == "2.0" then
|
169
|
+
return bypass(message, "com.acres4.common.info.JSONRPCRequest") if message.has_key? :params
|
170
|
+
return [message, "Error"] unless message[:error].nil?
|
171
|
+
return bypass(message, "com.acres4.common.info.JSONRPCResponse") if message.has_key? :result
|
172
|
+
end
|
173
|
+
|
174
|
+
[message, talk.guess_class(message)]
|
175
|
+
end
|
176
|
+
|
177
|
+
def bypass(object, type)
|
178
|
+
case type
|
179
|
+
when "com.acres4.common.info.NamedObjectWrapper"
|
180
|
+
return bypass(object[:body], object[:className])
|
181
|
+
when "com.acres4.common.info.JSONRPCRequest"
|
182
|
+
return bypass(object[:params], talk.guess_class(object[:params]))
|
183
|
+
when "com.acres4.common.info.JSONRPCResponse"
|
184
|
+
return bypass(object[:result], talk.guess_class(object[:result]))
|
185
|
+
else
|
186
|
+
return [object, type]
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
category "Talk"
|
2
|
+
description "Find references to a Talk class"
|
3
|
+
usage "class_name"
|
4
|
+
help "Lists all classes and protocols referencing a particular Talk class."
|
5
|
+
|
6
|
+
validate { args.length == 2 }
|
7
|
+
|
8
|
+
tab 0 do
|
9
|
+
talk.things_named_like(args[1], [:class]).map { |thing| talk.truncated_name thing[:name] }
|
10
|
+
end
|
11
|
+
|
12
|
+
run do
|
13
|
+
search_cls = talk.class_named(args[1])
|
14
|
+
return show_error("#{args[1]}: no such class in talk") if search_cls.nil?
|
15
|
+
full_name = search_cls[:name]
|
16
|
+
|
17
|
+
methods = list_methods(full_name)
|
18
|
+
fields = list_fields(full_name)
|
19
|
+
|
20
|
+
if methods.length > 0 then
|
21
|
+
puts "Methods (#{methods.length})"
|
22
|
+
methods.each { |method| show_method(method[:protocol], method[:method]) }
|
23
|
+
end
|
24
|
+
|
25
|
+
if fields.length > 0 then
|
26
|
+
puts "Fields (#{fields.length})"
|
27
|
+
fields.each { |field| show_field(field[:class], field[:field]) }
|
28
|
+
end
|
29
|
+
|
30
|
+
""
|
31
|
+
end
|
32
|
+
|
33
|
+
###
|
34
|
+
|
35
|
+
def list_methods(name)
|
36
|
+
methods = []
|
37
|
+
talk.definition[:protocol].each do |proto|
|
38
|
+
proto[:method].each do |method|
|
39
|
+
methods.push({protocol:proto, method:method}) if(talk.name_matches?(name, method[:request]) or talk.name_matches?(name, method[:response]))
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
methods
|
44
|
+
end
|
45
|
+
|
46
|
+
def list_fields(name)
|
47
|
+
fields = []
|
48
|
+
talk.definition[:class].each do |cls|
|
49
|
+
cls[:field].each do |field|
|
50
|
+
fields.push({class:cls, field:field}) if talk.name_matches?(name, field[:type].first)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
fields
|
55
|
+
end
|
56
|
+
|
57
|
+
def show_method(protocol, method)
|
58
|
+
puts "\t#{protocol[:name].style(:protcol_name)}: #{method[:name].style(:method_name)} #{method[:request].style(:class)} -> #{method[:response].style(:class)}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def show_field(cls, field)
|
62
|
+
puts "\t#{cls[:name].style(:class)}: #{field[:name].style(:field_name)}#{field[:type][1..-1].join("")}"
|
63
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
category "Protocol manipulation"
|
2
|
+
description "Select a connection for console"
|
3
|
+
usage "select [conenction]"
|
4
|
+
|
5
|
+
|
6
|
+
tab(0) { @shell.clients }
|
7
|
+
|
8
|
+
run do
|
9
|
+
if args.length == 1 then
|
10
|
+
(@shell.clients.sort.map { |key| render_client(key) }).join("\n")
|
11
|
+
else
|
12
|
+
return show_error("#{args[1]}: no such client") unless @shell.clients.include?(args[1].to_sym)
|
13
|
+
@shell.set_active_client(args[1])
|
14
|
+
""
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def render_client(key)
|
19
|
+
target = @shell.client(key)
|
20
|
+
fmt_key = sprintf("%20s", key.to_s)
|
21
|
+
host = usherm.host_for_url(target.uri) || "unknown host"
|
22
|
+
info = source_id(target)
|
23
|
+
|
24
|
+
"#{fmt_key.style(:target)}: #{info} #{target.uri.to_s.style(:url)} (#{host.style(:host)})"
|
25
|
+
end
|
26
|
+
|
27
|
+
def source_id(target)
|
28
|
+
app_info = target.server_info || {}
|
29
|
+
version = target.version || app_info[:currentTalkVersion]
|
30
|
+
protocol = sprintf("%-6s", (version || "")).style(:protocol_name)
|
31
|
+
name = sprintf("%20s", ((app_info[:appName] rescue nil) || "")).style(:name)
|
32
|
+
commit = sprintf("%7s", ((app_info[:sourceRevision][0..6] rescue nil) || "")).style(:commit)
|
33
|
+
branch = sprintf("%-30s", ((app_info[:supportingRevisions].first[:branches].first rescue nil) || "")).style(:branch)
|
34
|
+
|
35
|
+
"#{protocol} #{commit} #{branch}"
|
36
|
+
end
|