a4tools 1.2.7
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.
- 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
|