replicant-adb 0.0.1
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.
- data/.travis.yml +4 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +20 -0
- data/README.md +117 -0
- data/Rakefile +40 -0
- data/bin/replicant +11 -0
- data/lib/replicant.rb +9 -0
- data/lib/replicant/command.rb +352 -0
- data/lib/replicant/device.rb +20 -0
- data/lib/replicant/repl.rb +156 -0
- data/lib/replicant/styles.rb +30 -0
- data/lib/replicant/version.rb +3 -0
- data/test/commands/adb_command_spec.rb +58 -0
- data/test/commands/command_spec.rb +90 -0
- data/test/commands/command_spec_base.rb +30 -0
- data/test/commands/device_command_spec.rb +40 -0
- data/test/commands/devices_command_spec.rb +65 -0
- data/test/commands/env_command_spec.rb +30 -0
- data/test/commands/list_command_spec.rb +13 -0
- data/test/commands/package_command_spec.rb +34 -0
- data/test/helper.rb +16 -0
- metadata +137 -0
data/.travis.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
# Add dependencies required to use your gem here.
|
3
|
+
# Example:
|
4
|
+
# gem "activesupport", ">= 2.3.5"
|
5
|
+
|
6
|
+
# Add dependencies to develop your gem here.
|
7
|
+
# Include everything needed to run rake, tests, features, etc.
|
8
|
+
gem "activesupport-core-ext", "~> 4.0", :require => false
|
9
|
+
|
10
|
+
group :development do
|
11
|
+
gem "rake", "~> 10.1"
|
12
|
+
gem "bundler", "~> 1.0"
|
13
|
+
gem "jeweler", "~> 1.8.7"
|
14
|
+
end
|
15
|
+
|
16
|
+
group :test do
|
17
|
+
gem "minitest", "~> 5.0", :require => 'minitest/autorun'
|
18
|
+
gem "mocha", "~> 0.14.0", :require => 'mocha/setup'
|
19
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2013 Matthias Kaeppler
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
replicant - a repl for adb
|
2
|
+
==========================
|
3
|
+
|
4
|
+
`replicant` is an interactive shell (a [REPL][2]) for `adb`, the Android Debug Bridge.
|
5
|
+
It is partially based on Chris Wanstrath's excellent [repl][0] command line wrapper.
|
6
|
+
|
7
|
+

|
8
|
+
|
9
|
+
Overview
|
10
|
+
-------
|
11
|
+
Working with the `adb` tool directly to target connected emulators and devices is
|
12
|
+
verbose and cumbersome. `replicant` simplifies this process in a number of ways:
|
13
|
+
|
14
|
+
- being a repl, you're now working with `adb` in interactive mode
|
15
|
+
- allows fixing devices and package IDs for subsequent `adb` commands
|
16
|
+
- auto-detection of target package by project folder inspection
|
17
|
+
- command history and tab-completion via `rlwrap` (see below)
|
18
|
+
|
19
|
+
### Example
|
20
|
+
In this example session, we start `replicant` from the folder of an existing Android
|
21
|
+
application. It detects the manifest file and sets a default package ID.
|
22
|
+
From here on we list the available commands, fix the device to the first listed
|
23
|
+
emulator, uninstall the app, then reset the session.
|
24
|
+
|
25
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
26
|
+
v1.0.0
|
27
|
+
dP oo dP
|
28
|
+
88 88
|
29
|
+
88d888b. .d8888b. 88d888b. 88 dP .d8888b. .d8888b. 88d888b. d8888P
|
30
|
+
88' `88 88ooood8 88' `88 88 88 88' `"" 88' `88 88' `88 88
|
31
|
+
88 88. ... 88. .88 88 88 88. ... 88. .88 88 88 88
|
32
|
+
dP `88888P' 88Y888P' dP dP `88888P' `88888P8 dP dP dP
|
33
|
+
88
|
34
|
+
dP (c) 2013 Matthias Kaeppler
|
35
|
+
|
36
|
+
|
37
|
+
Type !list to see a list of commands.
|
38
|
+
Commands not starting in '!' are sent to adb verbatim.
|
39
|
+
Use Ctrl-D (i.e. EOF) to exit.
|
40
|
+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
41
|
+
Setting default package to "com.soundcloud.android"
|
42
|
+
-- com.soundcloud.android, No device set
|
43
|
+
>> !list
|
44
|
+
!device -- set a default device to work with
|
45
|
+
!devices -- print a list of connected devices
|
46
|
+
!list -- print a list of available commands
|
47
|
+
!package -- set a default package to work with
|
48
|
+
!reset -- clear current device and package
|
49
|
+
!restart -- restart ADB
|
50
|
+
OK.
|
51
|
+
-- com.soundcloud.android, No device set
|
52
|
+
>> !devices
|
53
|
+
|
54
|
+
005de387d71505d6 [Nexus 4]
|
55
|
+
emulator-5554 [Android SDK built for x86]
|
56
|
+
|
57
|
+
OK.
|
58
|
+
-- com.soundcloud.android, No device set
|
59
|
+
>> !device emu1
|
60
|
+
|
61
|
+
005de387d71505d6 [Nexus 4]
|
62
|
+
emulator-5554 [Android SDK built for x86]
|
63
|
+
|
64
|
+
Setting default device to emulator-5554 [Android SDK built for x86]
|
65
|
+
OK.
|
66
|
+
-- com.soundcloud.android, emulator-5554 [Android SDK built for x86]
|
67
|
+
>> uninstall
|
68
|
+
Success
|
69
|
+
OK.
|
70
|
+
-- com.soundcloud.android, emulator-5554 [Android SDK built for x86]
|
71
|
+
>> !reset
|
72
|
+
OK.
|
73
|
+
-- No package set, No device set
|
74
|
+
>>
|
75
|
+
|
76
|
+
Install
|
77
|
+
-------
|
78
|
+
`replicant` requires Ruby 1.9 and a UNIX/Linux compatible shell such as `bash` or `zsh`.
|
79
|
+
For the best experience, I strongly recommend to install [rlwrap][1] to get
|
80
|
+
command history and tab-completion, although it's not a requirement.
|
81
|
+
`replicant` integrates with `rlwrap` automatically;
|
82
|
+
it's sufficient for it to just be installed.
|
83
|
+
|
84
|
+
Replicant hasn't seen a stable release yet, so for now, has to be installed as a local Ruby gem.
|
85
|
+
To proceed, make sure that [bundler][3] is installed on your system. (e.g. `$gem install bundler`)
|
86
|
+
|
87
|
+
Clone this repository, then:
|
88
|
+
|
89
|
+
$ cd replicant
|
90
|
+
$ bundle install
|
91
|
+
$ rake install
|
92
|
+
|
93
|
+
|
94
|
+
Contributing
|
95
|
+
------------
|
96
|
+
|
97
|
+
[](https://travis-ci.org/mttkay/replicant)
|
98
|
+
|
99
|
+
Please hack on replicant and make it better and more feature complete!
|
100
|
+
|
101
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
102
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
103
|
+
* Fork the project.
|
104
|
+
* Start a feature branch to implement your bugfix or idea.
|
105
|
+
* Write an executable spec. See existing specs in the test/ folder for examples.
|
106
|
+
* Commit and push until you are happy with your contribution.
|
107
|
+
|
108
|
+
Copyright
|
109
|
+
---------
|
110
|
+
|
111
|
+
Copyright (c) 2013 Matthias Kaeppler. See LICENSE.txt for
|
112
|
+
further details.
|
113
|
+
|
114
|
+
[0]: https://github.com/defunkt/repl
|
115
|
+
[1]: http://utopia.knoware.nl/~hlub/rlwrap/
|
116
|
+
[2]: http://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop
|
117
|
+
[3]: http://bundler.io/
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
require './lib/replicant/version'
|
6
|
+
|
7
|
+
begin
|
8
|
+
Bundler.setup(:default, :development)
|
9
|
+
rescue Bundler::BundlerError => e
|
10
|
+
$stderr.puts e.message
|
11
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
12
|
+
exit e.status_code
|
13
|
+
end
|
14
|
+
require 'rake'
|
15
|
+
|
16
|
+
require 'jeweler'
|
17
|
+
Jeweler::Tasks.new do |gem|
|
18
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
19
|
+
gem.name = "replicant-adb"
|
20
|
+
gem.version = Replicant::VERSION
|
21
|
+
gem.homepage = "https://github.com/mttkay/replicant"
|
22
|
+
gem.license = "MIT"
|
23
|
+
gem.summary = "A REPL for the Android Debug Bridge"
|
24
|
+
gem.description = "replicant is an interactive shell (a REPL) for ADB, the Android Debug Bridge"
|
25
|
+
gem.email = "m.kaeppler@gmail.com"
|
26
|
+
gem.authors = ["Matthias Kaeppler"]
|
27
|
+
gem.files.exclude 'screenshots/**'
|
28
|
+
# dependencies defined in Gemfile
|
29
|
+
end
|
30
|
+
Jeweler::RubygemsDotOrgTasks.new
|
31
|
+
|
32
|
+
require 'rake/testtask'
|
33
|
+
Rake::TestTask.new(:test) do |test|
|
34
|
+
test.libs << 'lib' << 'test'
|
35
|
+
test.pattern = 'test/**/*_spec.rb'
|
36
|
+
test.verbose = true
|
37
|
+
end
|
38
|
+
|
39
|
+
task :default => :test
|
40
|
+
|
data/bin/replicant
ADDED
data/lib/replicant.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'active_support/core_ext/object/blank.rb'
|
2
|
+
require 'active_support/core_ext/string/filters.rb'
|
3
|
+
require 'active_support/core_ext/object/try.rb'
|
4
|
+
|
5
|
+
require 'replicant/version'
|
6
|
+
require 'replicant/command'
|
7
|
+
require 'replicant/styles'
|
8
|
+
require 'replicant/device'
|
9
|
+
require 'replicant/repl'
|
@@ -0,0 +1,352 @@
|
|
1
|
+
require 'stringio'
|
2
|
+
|
3
|
+
class Command
|
4
|
+
|
5
|
+
def self.inherited(subclass)
|
6
|
+
@@subclasses ||= []
|
7
|
+
@@subclasses << subclass
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.all
|
11
|
+
(@@subclasses - [AdbCommand, ListCommand, EnvCommand]).map do |clazz|
|
12
|
+
clazz.new(nil)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.load(repl, command_line)
|
17
|
+
if command_line == '!'
|
18
|
+
# load command that lists available commands
|
19
|
+
ListCommand.new(repl)
|
20
|
+
elsif command_line == '?'
|
21
|
+
EnvCommand.new(repl)
|
22
|
+
elsif command_line.start_with?('!')
|
23
|
+
# load custom command
|
24
|
+
command_parts = command_line[1..-1].split
|
25
|
+
command_name = command_parts.first
|
26
|
+
command_args = command_parts[1..-1].join(' ')
|
27
|
+
command_class = "#{command_name.capitalize}Command"
|
28
|
+
begin
|
29
|
+
clazz = Object.const_get(command_class)
|
30
|
+
clazz.new(repl, command_args)
|
31
|
+
rescue NameError => e
|
32
|
+
nil
|
33
|
+
end
|
34
|
+
else
|
35
|
+
# forward command to ADB
|
36
|
+
AdbCommand.new(repl, command_line.strip)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
attr_reader :args
|
41
|
+
|
42
|
+
def initialize(repl, args = nil, options = {})
|
43
|
+
@repl = repl
|
44
|
+
@args = args.strip if args
|
45
|
+
@options = options
|
46
|
+
end
|
47
|
+
|
48
|
+
def name
|
49
|
+
"!#{self.class.name.gsub("Command", "").downcase}"
|
50
|
+
end
|
51
|
+
|
52
|
+
# subclasses override this to provide a description of their functionality
|
53
|
+
def description
|
54
|
+
"TODO: description missing"
|
55
|
+
end
|
56
|
+
|
57
|
+
# subclasses override this to provide a usage example
|
58
|
+
def usage
|
59
|
+
end
|
60
|
+
|
61
|
+
def execute
|
62
|
+
if valid_args?
|
63
|
+
run
|
64
|
+
else
|
65
|
+
output "Invalid arguments. Ex.: #{usage}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def valid_args?
|
72
|
+
true
|
73
|
+
end
|
74
|
+
|
75
|
+
def output(message)
|
76
|
+
puts message unless @options[:silent]
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
class AdbCommand < Command
|
82
|
+
|
83
|
+
# the command line program
|
84
|
+
ADB = 'adb'
|
85
|
+
|
86
|
+
def run
|
87
|
+
begin
|
88
|
+
cmd = "#{adb} #{args}"
|
89
|
+
|
90
|
+
if interactive?
|
91
|
+
system cmd
|
92
|
+
else
|
93
|
+
cmd << " #{@repl.default_package}" if @repl.default_package && package_dependent?
|
94
|
+
output cmd if @repl.debug?
|
95
|
+
result = `#{cmd}`
|
96
|
+
output result
|
97
|
+
result
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
def adb
|
105
|
+
adb = "#{ADB}"
|
106
|
+
adb << " -s #{@repl.default_device.id}" if @repl.default_device
|
107
|
+
adb
|
108
|
+
end
|
109
|
+
|
110
|
+
def interactive?
|
111
|
+
args == "shell" || args.start_with?("logcat")
|
112
|
+
end
|
113
|
+
|
114
|
+
def package_dependent?
|
115
|
+
["uninstall"].include?(args)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
class DevicesCommand < Command
|
120
|
+
def description
|
121
|
+
"print a list of connected devices"
|
122
|
+
end
|
123
|
+
|
124
|
+
def run
|
125
|
+
adb = AdbCommand.new(@repl, "devices -l", :silent => true)
|
126
|
+
device_lines = adb.execute.lines.to_a.reject do |line|
|
127
|
+
line.strip.empty? || line.include?("daemon") || line.include?("List of devices")
|
128
|
+
end
|
129
|
+
|
130
|
+
device_ids = device_lines.map { |l| /([\S]+)\s+device/.match(l)[1] }
|
131
|
+
device_products = device_lines.map { |l| /product:([\S]+)/.match(l).try(:[], 1) }
|
132
|
+
|
133
|
+
device_names = device_lines.zip(device_ids).map do |l, id|
|
134
|
+
/model:([\S]+)/.match(l).try(:[], 1) || detect_device_name(id)
|
135
|
+
end
|
136
|
+
|
137
|
+
devices = device_ids.zip(device_names, device_products).map do |id, name, product|
|
138
|
+
Device.new(id, humanize_name(name, product))
|
139
|
+
end
|
140
|
+
|
141
|
+
output ""
|
142
|
+
output devices_string(devices)
|
143
|
+
output ""
|
144
|
+
devices
|
145
|
+
end
|
146
|
+
|
147
|
+
private
|
148
|
+
|
149
|
+
def detect_device_name(id)
|
150
|
+
if id.start_with?("emulator-")
|
151
|
+
"Android emulator"
|
152
|
+
else
|
153
|
+
"Unknown device"
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
def humanize_name(name_string, product)
|
158
|
+
if product == "vbox86p"
|
159
|
+
"Genymotion " + name_string.gsub(/___[\d_]+___/, "_")
|
160
|
+
else
|
161
|
+
name_string
|
162
|
+
end.gsub('_', ' ').squish
|
163
|
+
end
|
164
|
+
|
165
|
+
def devices_string(devices)
|
166
|
+
device_string = if devices.any?
|
167
|
+
padding = devices.map { |d| d.name.length }.max
|
168
|
+
indices = (0..devices.length - 1).to_a
|
169
|
+
indices.zip(devices).map { |i, d| "[#{i}] #{d.name}#{' ' * (padding - d.name.length)} | #{d.id}" }
|
170
|
+
else
|
171
|
+
"No devices found"
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
class PackageCommand < Command
|
177
|
+
|
178
|
+
def description
|
179
|
+
"set a default package to work with"
|
180
|
+
end
|
181
|
+
|
182
|
+
def usage
|
183
|
+
"#{name} com.mydomain.mypackage"
|
184
|
+
end
|
185
|
+
|
186
|
+
def valid_args?
|
187
|
+
args.present? && /^\w+(\.\w+)*$/ =~ args
|
188
|
+
end
|
189
|
+
|
190
|
+
def run
|
191
|
+
output "Setting default package to #{args.inspect}"
|
192
|
+
@repl.default_package = args
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
class DeviceCommand < Command
|
197
|
+
def description
|
198
|
+
"set a default device to work with"
|
199
|
+
end
|
200
|
+
|
201
|
+
def usage
|
202
|
+
"#{name} [<index>|<device_id>]"
|
203
|
+
end
|
204
|
+
|
205
|
+
def valid_args?
|
206
|
+
args.present? && /\S+/ =~ args
|
207
|
+
end
|
208
|
+
|
209
|
+
def run
|
210
|
+
default_device = if index?
|
211
|
+
# user selected by index
|
212
|
+
devices[args.to_i]
|
213
|
+
else
|
214
|
+
# user selected by device ID
|
215
|
+
devices.detect { |d| d.id == args }
|
216
|
+
end
|
217
|
+
|
218
|
+
if default_device
|
219
|
+
output "Setting default device to #{default_device.inspect}"
|
220
|
+
@repl.default_device = default_device
|
221
|
+
else
|
222
|
+
output "No such device"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
|
226
|
+
private
|
227
|
+
|
228
|
+
def index?
|
229
|
+
/^\d+$/ =~ args
|
230
|
+
end
|
231
|
+
|
232
|
+
def devices
|
233
|
+
@devices ||= DevicesCommand.new(@repl, nil, :silent => true).execute
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
237
|
+
|
238
|
+
class ResetCommand < Command
|
239
|
+
|
240
|
+
def description
|
241
|
+
"clear current device and package"
|
242
|
+
end
|
243
|
+
|
244
|
+
def valid_args?
|
245
|
+
args.blank?
|
246
|
+
end
|
247
|
+
|
248
|
+
def run
|
249
|
+
@repl.default_device = nil
|
250
|
+
@repl.default_package = nil
|
251
|
+
end
|
252
|
+
|
253
|
+
end
|
254
|
+
|
255
|
+
class ListCommand < Command
|
256
|
+
def valid_args?
|
257
|
+
args.blank?
|
258
|
+
end
|
259
|
+
|
260
|
+
def description
|
261
|
+
"print a list of available commands"
|
262
|
+
end
|
263
|
+
|
264
|
+
def run
|
265
|
+
command_list = Command.all.sort_by {|c| c.name}.map do |command|
|
266
|
+
padding = 20 - command.name.length
|
267
|
+
desc = "#{command.name} #{' ' * padding} -- #{command.description}"
|
268
|
+
desc
|
269
|
+
end
|
270
|
+
output command_list.join("\n")
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
class RestartCommand < Command
|
275
|
+
def description
|
276
|
+
"restart ADB"
|
277
|
+
end
|
278
|
+
|
279
|
+
def run
|
280
|
+
# Faster than kill-server, and also catches ADB instances launched by
|
281
|
+
# IntelliJ. Moreover, start-server after kill-server sometimes makes the
|
282
|
+
# server fail to start up unless you sleep for a second or so
|
283
|
+
`killall adb`
|
284
|
+
AdbCommand.new(@repl, "start-server").execute
|
285
|
+
end
|
286
|
+
end
|
287
|
+
|
288
|
+
class LogcatCommand < Command
|
289
|
+
|
290
|
+
def description
|
291
|
+
"access device logs"
|
292
|
+
end
|
293
|
+
|
294
|
+
def valid_args?
|
295
|
+
args.blank?
|
296
|
+
end
|
297
|
+
|
298
|
+
def run
|
299
|
+
pid = if @repl.default_package
|
300
|
+
processes = AdbCommand.new(@repl, "shell ps", :silent => true).execute
|
301
|
+
pid_line = processes.lines.detect {|l| l.include?(@repl.default_package)}
|
302
|
+
pid_line.split[1].strip if pid_line
|
303
|
+
end
|
304
|
+
|
305
|
+
logcat = "logcat -v time"
|
306
|
+
logcat << " | grep -E '\(\s*#{pid}\)'"
|
307
|
+
AdbCommand.new(@repl, logcat).execute
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
class ClearCommand < Command
|
312
|
+
|
313
|
+
def description
|
314
|
+
"clear application data"
|
315
|
+
end
|
316
|
+
|
317
|
+
# TODO: this is not a very good argument validator
|
318
|
+
def valid_args?
|
319
|
+
args.present? || @repl.default_package
|
320
|
+
end
|
321
|
+
|
322
|
+
def usage
|
323
|
+
"#{name} [com.example.package|<empty>(when default package is set)]"
|
324
|
+
end
|
325
|
+
|
326
|
+
def run
|
327
|
+
package = args.present? ? args : @repl.default_package
|
328
|
+
# Clear app data - cache, SharedPreferences, Databases
|
329
|
+
AdbCommand.new(@repl, "shell su -c \"rm -r /data/data/#{package}/*\"").execute
|
330
|
+
# Force application stop to recreate shared preferences, databases with new launch
|
331
|
+
AdbCommand.new(@repl, "shell am force-stop #{package}").execute
|
332
|
+
end
|
333
|
+
end
|
334
|
+
|
335
|
+
class EnvCommand < Command
|
336
|
+
|
337
|
+
def valid_args?
|
338
|
+
args.blank?
|
339
|
+
end
|
340
|
+
|
341
|
+
def run
|
342
|
+
env = "Package: #{@repl.default_package || 'Not set'}\n"
|
343
|
+
env << "Device: "
|
344
|
+
device = @repl.default_device
|
345
|
+
env << if device
|
346
|
+
"#{device.name} (#{device.id})"
|
347
|
+
else
|
348
|
+
'Not set'
|
349
|
+
end
|
350
|
+
output env
|
351
|
+
end
|
352
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class Device
|
2
|
+
attr_reader :id, :name
|
3
|
+
|
4
|
+
def initialize(id, name)
|
5
|
+
@id = id
|
6
|
+
@name = name
|
7
|
+
end
|
8
|
+
|
9
|
+
def emulator?
|
10
|
+
@id.start_with?('emulator-')
|
11
|
+
end
|
12
|
+
|
13
|
+
def physical_device?
|
14
|
+
!emulator?
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
"#{@id} [#{@name}]"
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'find'
|
2
|
+
require 'rexml/document'
|
3
|
+
|
4
|
+
module Replicant
|
5
|
+
class REPL
|
6
|
+
|
7
|
+
include Styles
|
8
|
+
|
9
|
+
# for auto-complete via rlwrap; should probably go in Rakefile at some point
|
10
|
+
ADB_COMMANDS = %w(devices connect disconnect push pull sync shell emu logcat
|
11
|
+
forward jdwp install uninstall bugreport backup restore help version
|
12
|
+
wait-for-device start-server kill-server get-state get-serialno get-devpath
|
13
|
+
status-window remount reboot reboot-bootloader root usb tcpip ppp)
|
14
|
+
|
15
|
+
attr_accessor :default_package
|
16
|
+
attr_accessor :default_device
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
end
|
20
|
+
|
21
|
+
def run
|
22
|
+
# must be the first thing to execute, since it wraps the script
|
23
|
+
setup_rlwrap
|
24
|
+
|
25
|
+
show_greeting
|
26
|
+
if ARGV.any? { |arg| %w( -h --help -help help ).include?(arg) }
|
27
|
+
show_help
|
28
|
+
end
|
29
|
+
|
30
|
+
# try to detect a default package to work with from an AndroidManifest
|
31
|
+
# file somewhere close by
|
32
|
+
if manifest_path = detect_android_manifest_path
|
33
|
+
app_package = get_package_from_manifest(manifest_path)
|
34
|
+
PackageCommand.new(self, app_package).execute
|
35
|
+
end
|
36
|
+
|
37
|
+
# reset terminal colors on exit
|
38
|
+
at_exit { puts unstyled }
|
39
|
+
|
40
|
+
loop do
|
41
|
+
command_loop
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def command_loop
|
46
|
+
print prompt
|
47
|
+
|
48
|
+
begin
|
49
|
+
command_line = $stdin.gets.chomp
|
50
|
+
rescue NoMethodError, Interrupt
|
51
|
+
exit
|
52
|
+
end
|
53
|
+
|
54
|
+
return if command_line.strip.empty?
|
55
|
+
|
56
|
+
command = Command.load(self, command_line)
|
57
|
+
if command
|
58
|
+
command.execute
|
59
|
+
puts span("OK.", :white_fg, :bold) { unstyled }
|
60
|
+
else
|
61
|
+
puts "No such command"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def debug?
|
66
|
+
ARGV.include?('--debug')
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
def prompt
|
72
|
+
prompt = ENV['REPL_PROMPT'] || begin
|
73
|
+
span('>> ', :white_fg, :bold) { styled(:green_fg) }
|
74
|
+
end.lstrip
|
75
|
+
end
|
76
|
+
|
77
|
+
def show_greeting
|
78
|
+
style = styled(:white_fg, :black_bg, :bold)
|
79
|
+
green = lambda { |text| span(text, :green_fg) { style } }
|
80
|
+
|
81
|
+
logo = <<-logo
|
82
|
+
dP oo dP
|
83
|
+
88 88
|
84
|
+
88d888b. .d8888b. 88d888b. 88 dP .d8888b. .d8888b. 88d888b. d8888P
|
85
|
+
88' `88 88ooood8 88' `88 88 88 88' `"" 88' `88 88' `88 88
|
86
|
+
88 88. ... 88. .88 88 88 88. ... 88. .88 88 88 88
|
87
|
+
dP `88888P' 88Y888P' dP dP `88888P' `88888P8 dP dP dP
|
88
|
+
88
|
89
|
+
dP (c) 2013 Matthias Kaeppler
|
90
|
+
logo
|
91
|
+
puts style + ("~" * 70)
|
92
|
+
puts " v" + Replicant::VERSION
|
93
|
+
puts green[logo]
|
94
|
+
puts ""
|
95
|
+
puts " Type '#{green['!']}' to see a list of commands, '#{green['?']}' for environment info."
|
96
|
+
puts " Commands not starting in '#{green['!']}' are sent to adb verbatim."
|
97
|
+
puts " Use #{green['Ctrl-D']} (i.e. EOF) to exit."
|
98
|
+
puts ("~" * 70) + unstyled
|
99
|
+
end
|
100
|
+
|
101
|
+
def show_help
|
102
|
+
puts <<-help
|
103
|
+
Usage: replicant [options]
|
104
|
+
|
105
|
+
Options:
|
106
|
+
--help Display this message
|
107
|
+
--debug Display debug info
|
108
|
+
|
109
|
+
Bug reports, suggestions, updates:
|
110
|
+
http://github.com/mttkay/replicant/issues
|
111
|
+
help
|
112
|
+
exit
|
113
|
+
end
|
114
|
+
|
115
|
+
def setup_rlwrap
|
116
|
+
if !ENV['__REPL_WRAPPED'] && system("which rlwrap > /dev/null 2> /dev/null")
|
117
|
+
ENV['__REPL_WRAPPED'] = '0'
|
118
|
+
|
119
|
+
# set up auto-completion commands
|
120
|
+
completion_file = File.expand_path('~/.adb_completion')
|
121
|
+
File.open(completion_file, 'w') { |file| file.write(ADB_COMMANDS.join(' ')) }
|
122
|
+
|
123
|
+
# set up command history
|
124
|
+
if File.exists?(history_dir = File.expand_path(ENV['REPL_HISTORY_DIR'] || "~/"))
|
125
|
+
history_file = "#{history_dir}/.adb_history"
|
126
|
+
end
|
127
|
+
|
128
|
+
rlargs = "-c" # complete file names
|
129
|
+
rlargs << " --break-chars=" # we don't want to break any of the commands
|
130
|
+
rlargs << " -f #{completion_file}" if completion_file
|
131
|
+
rlargs << " -H #{history_file}" if history_file
|
132
|
+
|
133
|
+
exec "rlwrap #{rlargs} #$0 #{ARGV.join(' ')}"
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# best effort function to detect the manifest path.
|
138
|
+
# checks for well known locations and falls back to a recursive search with
|
139
|
+
# a maximum depth of 2 directory levels
|
140
|
+
def detect_android_manifest_path
|
141
|
+
manifest_file = 'AndroidManifest.xml'
|
142
|
+
known_locations = %W(./#{manifest_file} ./src/main/#{manifest_file})
|
143
|
+
known_locations.find {|loc| File.exist?(loc)} || begin
|
144
|
+
Find.find('.') do |path|
|
145
|
+
Find.prune if path.start_with?('./.') || path.split('/').size > 3
|
146
|
+
return path if path.include?(manifest_file)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
def get_package_from_manifest(manifest_path)
|
152
|
+
manifest = REXML::Document.new(File.new(manifest_path))
|
153
|
+
manifest.root.attributes['package']
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Styles
|
2
|
+
|
3
|
+
STYLES = {
|
4
|
+
# foreground text
|
5
|
+
:white_fg => 37,
|
6
|
+
:black_fg => 30,
|
7
|
+
:green_fg => 32,
|
8
|
+
|
9
|
+
# background
|
10
|
+
:white_bg => 47,
|
11
|
+
|
12
|
+
# text styles
|
13
|
+
:bold => 1
|
14
|
+
}
|
15
|
+
|
16
|
+
def span(text, *styles)
|
17
|
+
styled = "\e[#{STYLES.values_at(*styles).join(';')}m#{text}"
|
18
|
+
styled << yield if block_given?
|
19
|
+
styled
|
20
|
+
end
|
21
|
+
|
22
|
+
def styled(*styles)
|
23
|
+
"\e[#{STYLES.values_at(*styles).join(';')}m"
|
24
|
+
end
|
25
|
+
|
26
|
+
def unstyled
|
27
|
+
"\e[0m"
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class AdbCommandSpec < CommandSpecBase
|
4
|
+
|
5
|
+
describe "a basic adb command" do
|
6
|
+
before do
|
7
|
+
@command = silent AdbCommand.new(@repl, "devices")
|
8
|
+
@command.execute
|
9
|
+
end
|
10
|
+
|
11
|
+
it "sends a command to adb and captures the output" do
|
12
|
+
@command.backtick_capture.must_equal "adb devices"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "does not use Kernel#system" do
|
16
|
+
@command.system_capture.must_be_nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "an interactive command" do
|
21
|
+
before do
|
22
|
+
@command = AdbCommand.new(@repl, "shell")
|
23
|
+
@command.execute
|
24
|
+
end
|
25
|
+
|
26
|
+
it "is executed using a Kernel#system call" do
|
27
|
+
@command.system_capture.must_equal "adb shell"
|
28
|
+
end
|
29
|
+
|
30
|
+
describe "when it's 'shell'" do
|
31
|
+
it "is not treated as interactive when arguments are present" do
|
32
|
+
command = AdbCommand.new(@repl, "shell ps")
|
33
|
+
command.execute
|
34
|
+
command.system_capture.must_be_nil
|
35
|
+
command.backtick_capture.must_equal "adb shell ps"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe "with a default package set" do
|
41
|
+
before do
|
42
|
+
@repl.stubs(:default_package).returns("com.myapp")
|
43
|
+
end
|
44
|
+
|
45
|
+
it "does not set the default package if command is not package dependent" do
|
46
|
+
command = silent AdbCommand.new(@repl, "devices")
|
47
|
+
command.execute
|
48
|
+
command.backtick_capture.must_equal "adb devices"
|
49
|
+
end
|
50
|
+
|
51
|
+
it "adds the default package if command is package dependent" do
|
52
|
+
command = silent AdbCommand.new(@repl, "uninstall")
|
53
|
+
command.execute
|
54
|
+
command.backtick_capture.must_equal "adb uninstall com.myapp"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class CommandSpec < CommandSpecBase
|
4
|
+
|
5
|
+
describe "command loading" do
|
6
|
+
it "loads the EnvCommand when command is '?'" do
|
7
|
+
command = Command.load(@repl, "?")
|
8
|
+
command.must_be_instance_of EnvCommand
|
9
|
+
end
|
10
|
+
|
11
|
+
it "loads the ListCommand when command is '!'" do
|
12
|
+
command = Command.load(@repl, "!")
|
13
|
+
command.must_be_instance_of ListCommand
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns nil if !-command cannot be resolved" do
|
17
|
+
Command.load(@repl, "!unknown").must_be_nil
|
18
|
+
end
|
19
|
+
|
20
|
+
it "loads the AdbCommand for commands not starting in '!'" do
|
21
|
+
command = Command.load(@repl, "shell ps")
|
22
|
+
command.must_be_instance_of AdbCommand
|
23
|
+
end
|
24
|
+
|
25
|
+
describe "arguments" do
|
26
|
+
it "injects the arguments for loaded command objects" do
|
27
|
+
command = Command.load(@repl, "shell ps")
|
28
|
+
command.args.must_equal "shell ps"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "command listing" do
|
34
|
+
it "does not include the ListCommand" do
|
35
|
+
Command.all.map{|c|c.class}.wont_include(ListCommand)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "does not include the AdbCommand" do
|
39
|
+
Command.all.map{|c|c.class}.wont_include(AdbCommand)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "does not include the EnvCommand" do
|
43
|
+
Command.all.map{|c|c.class}.wont_include(EnvCommand)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "the command interface" do
|
48
|
+
before do
|
49
|
+
class ::TestCommand < Command; end
|
50
|
+
@command = silent TestCommand.new(@repl)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "allows resolving the command name via type inspection" do
|
54
|
+
@command.name.must_equal "!test"
|
55
|
+
end
|
56
|
+
|
57
|
+
it "triggers the run method when calling 'execute'" do
|
58
|
+
@command.expects(:run).once
|
59
|
+
@command.execute
|
60
|
+
end
|
61
|
+
|
62
|
+
it "does not trigger the run method when arguments are invalid" do
|
63
|
+
@command.expects(:valid_args?).returns(false)
|
64
|
+
@command.expects(:run).never
|
65
|
+
@command.execute
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
describe "command options" do
|
70
|
+
before do
|
71
|
+
class ::TestCommand < Command
|
72
|
+
def run
|
73
|
+
output "this is a test"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it "can silence console output" do
|
79
|
+
command = TestCommand.new(@repl, nil, :silent => true)
|
80
|
+
lambda { command.execute }.must_be_silent
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
describe "arguments" do
|
85
|
+
it "strips argument whitespace when creating command instance" do
|
86
|
+
command = TestCommand.new(@repl, " ")
|
87
|
+
command.args.must_equal ""
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
class Command
|
2
|
+
attr_reader :backtick_capture
|
3
|
+
attr_reader :system_capture
|
4
|
+
# capture system calls on all commands
|
5
|
+
def `(cmd)
|
6
|
+
@backtick_capture = cmd
|
7
|
+
nil
|
8
|
+
end
|
9
|
+
def system(cmd)
|
10
|
+
@system_capture = cmd
|
11
|
+
nil
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class CommandSpecBase < MiniTest::Spec
|
16
|
+
|
17
|
+
before do
|
18
|
+
@repl = Replicant::REPL.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def silent(command)
|
22
|
+
def command.output(s)
|
23
|
+
@output = s
|
24
|
+
end
|
25
|
+
def command.output_capture
|
26
|
+
@output
|
27
|
+
end
|
28
|
+
command
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class DeviceCommandSpec < CommandSpecBase
|
4
|
+
|
5
|
+
describe "given a valid list of devices" do
|
6
|
+
before do
|
7
|
+
@device0 = Device.new("emulator-5554", "Emulator 1")
|
8
|
+
@device1 = Device.new("emulator-5556", "Emulator 2")
|
9
|
+
DevicesCommand.any_instance.stubs(:execute).returns([@device0, @device1])
|
10
|
+
end
|
11
|
+
|
12
|
+
it "can select a device by index" do
|
13
|
+
silent(DeviceCommand.new(@repl, "0")).execute
|
14
|
+
@repl.default_device.must_equal @device0
|
15
|
+
silent(DeviceCommand.new(@repl, "1")).execute
|
16
|
+
@repl.default_device.must_equal @device1
|
17
|
+
end
|
18
|
+
|
19
|
+
it "can select a device by device id" do
|
20
|
+
silent(DeviceCommand.new(@repl, "emulator-5554")).execute
|
21
|
+
@repl.default_device.must_equal @device0
|
22
|
+
silent(DeviceCommand.new(@repl, "emulator-5556")).execute
|
23
|
+
@repl.default_device.must_equal @device1
|
24
|
+
end
|
25
|
+
|
26
|
+
it "outputs an error message if selected device doesn't exist" do
|
27
|
+
command = DeviceCommand.new(@repl, "emulator-bogus")
|
28
|
+
lambda { command.execute }.must_output "No such device\n"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "given an empty list of devices" do
|
33
|
+
it "outputs an error message when selecting a device" do
|
34
|
+
DevicesCommand.any_instance.stubs(:execute).returns([])
|
35
|
+
command = DeviceCommand.new(@repl, "emulator-5554")
|
36
|
+
lambda { command.execute }.must_output "No such device\n"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
ADB_NO_DEVICES = <<-OUTPUT
|
4
|
+
* daemon not running. starting it now on port 5037 *
|
5
|
+
* daemon started successfully *
|
6
|
+
List of devices attached
|
7
|
+
|
8
|
+
OUTPUT
|
9
|
+
|
10
|
+
ADB_DEVICES = <<-OUTPUT
|
11
|
+
* daemon not running. starting it now on port 5037 *
|
12
|
+
* daemon started successfully *
|
13
|
+
List of devices attached
|
14
|
+
192.168.56.101:5555 device product:vbox86p model:Nexus_4___4_3___API_18___768x1280 device:vbox86p
|
15
|
+
005de387d71505d6 device usb:1D110000 product:occam model:Nexus_4 device:mako
|
16
|
+
emulator-5554 device
|
17
|
+
|
18
|
+
OUTPUT
|
19
|
+
|
20
|
+
REPLICANT_DEVICES = <<-OUTPUT
|
21
|
+
|
22
|
+
[0] Genymotion Nexus 4 API 18 768x1280 | 192.168.56.101:5555
|
23
|
+
[1] Nexus 4 | 005de387d71505d6
|
24
|
+
[2] Android emulator | emulator-5554
|
25
|
+
|
26
|
+
OUTPUT
|
27
|
+
|
28
|
+
class DevicesCommandSpec < CommandSpecBase
|
29
|
+
|
30
|
+
describe "when no devices were found" do
|
31
|
+
before do
|
32
|
+
AdbCommand.any_instance.expects(:execute).returns(ADB_NO_DEVICES)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "returns an empty device list" do
|
36
|
+
command = silent DevicesCommand.new(@repl)
|
37
|
+
command.execute.must_equal []
|
38
|
+
end
|
39
|
+
|
40
|
+
it "prints a message and exits" do
|
41
|
+
command = DevicesCommand.new(@repl)
|
42
|
+
lambda { command.execute }.must_output("\nNo devices found\n\n")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "when devices were found" do
|
47
|
+
before do
|
48
|
+
AdbCommand.any_instance.expects(:execute).returns(ADB_DEVICES)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "returns the list of devices" do
|
52
|
+
command = silent DevicesCommand.new(@repl)
|
53
|
+
devices = command.execute
|
54
|
+
devices.map { |d| d.id }.must_equal [
|
55
|
+
"192.168.56.101:5555", "005de387d71505d6", "emulator-5554"
|
56
|
+
]
|
57
|
+
end
|
58
|
+
|
59
|
+
it "outputs a prettified, indexed list of devices" do
|
60
|
+
command = DevicesCommand.new(@repl)
|
61
|
+
lambda { command.execute }.must_output(REPLICANT_DEVICES)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class EnvCommandSpec < CommandSpecBase
|
4
|
+
|
5
|
+
BLANK_ENV_OUTPUT = <<-OUT
|
6
|
+
Package: Not set
|
7
|
+
Device: Not set
|
8
|
+
OUT
|
9
|
+
|
10
|
+
ACTIVE_ENV_OUTPUT = <<-OUT
|
11
|
+
Package: com.myapp
|
12
|
+
Device: Emulator 1 (emulator-5554)
|
13
|
+
OUT
|
14
|
+
|
15
|
+
describe "with no device and no package fixed" do
|
16
|
+
it "lists no devices and packages as selected" do
|
17
|
+
command = EnvCommand.new(@repl)
|
18
|
+
lambda { command.execute }.must_output(BLANK_ENV_OUTPUT)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
describe "with a device and package fixed" do
|
23
|
+
it "lists no devices and packages as selected" do
|
24
|
+
@repl.default_device = Device.new("emulator-5554", "Emulator 1")
|
25
|
+
@repl.default_package = 'com.myapp'
|
26
|
+
command = EnvCommand.new(@repl)
|
27
|
+
lambda { command.execute }.must_output(ACTIVE_ENV_OUTPUT)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class ListCommandSpec < CommandSpecBase
|
4
|
+
|
5
|
+
describe "listing commands" do
|
6
|
+
it "prints a list of available commands" do
|
7
|
+
command = silent ListCommand.new(@repl)
|
8
|
+
command.execute
|
9
|
+
command.output_capture.must_match /^!\w+/
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class PackageCommandSpec < CommandSpecBase
|
4
|
+
|
5
|
+
describe "with valid arguments" do
|
6
|
+
it "accepts a package with one element" do
|
7
|
+
command = silent PackageCommand.new(@repl, "myapp")
|
8
|
+
command.execute
|
9
|
+
@repl.default_package.must_equal "myapp"
|
10
|
+
end
|
11
|
+
|
12
|
+
it "accepts a package with two elements" do
|
13
|
+
command = silent PackageCommand.new(@repl, "com.myapp")
|
14
|
+
command.execute
|
15
|
+
@repl.default_package.must_equal "com.myapp"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "with invalid arguments" do
|
20
|
+
it "refuses a package not starting with alphanumeric characters" do
|
21
|
+
command = silent PackageCommand.new(@repl, "%myapp")
|
22
|
+
command.expects(:run).never
|
23
|
+
command.execute
|
24
|
+
@repl.default_package.must_be_nil
|
25
|
+
end
|
26
|
+
|
27
|
+
it "refuses a package containing non-alphanumeric characters" do
|
28
|
+
command = silent PackageCommand.new(@repl, "myapp.some$thing")
|
29
|
+
command.expects(:run).never
|
30
|
+
command.execute
|
31
|
+
@repl.default_package.must_be_nil
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
begin
|
2
|
+
Bundler.require(:default, :test)
|
3
|
+
rescue Bundler::BundlerError => e
|
4
|
+
$stderr.puts e.message
|
5
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
6
|
+
exit e.status_code
|
7
|
+
end
|
8
|
+
|
9
|
+
# additional test modules
|
10
|
+
require 'minitest/pride'
|
11
|
+
require 'commands/command_spec_base'
|
12
|
+
|
13
|
+
# application module
|
14
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
15
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
16
|
+
require 'replicant'
|
metadata
ADDED
@@ -0,0 +1,137 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: replicant-adb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Matthias Kaeppler
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-11-27 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activesupport-core-ext
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ~>
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '4.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ~>
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '10.1'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '10.1'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bundler
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ~>
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '1.0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: jeweler
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ~>
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 1.8.7
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ~>
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 1.8.7
|
78
|
+
description: replicant is an interactive shell (a REPL) for ADB, the Android Debug
|
79
|
+
Bridge
|
80
|
+
email: m.kaeppler@gmail.com
|
81
|
+
executables:
|
82
|
+
- replicant
|
83
|
+
extensions: []
|
84
|
+
extra_rdoc_files:
|
85
|
+
- LICENSE.txt
|
86
|
+
- README.md
|
87
|
+
files:
|
88
|
+
- .travis.yml
|
89
|
+
- Gemfile
|
90
|
+
- LICENSE.txt
|
91
|
+
- README.md
|
92
|
+
- Rakefile
|
93
|
+
- bin/replicant
|
94
|
+
- lib/replicant.rb
|
95
|
+
- lib/replicant/command.rb
|
96
|
+
- lib/replicant/device.rb
|
97
|
+
- lib/replicant/repl.rb
|
98
|
+
- lib/replicant/styles.rb
|
99
|
+
- lib/replicant/version.rb
|
100
|
+
- test/commands/adb_command_spec.rb
|
101
|
+
- test/commands/command_spec.rb
|
102
|
+
- test/commands/command_spec_base.rb
|
103
|
+
- test/commands/device_command_spec.rb
|
104
|
+
- test/commands/devices_command_spec.rb
|
105
|
+
- test/commands/env_command_spec.rb
|
106
|
+
- test/commands/list_command_spec.rb
|
107
|
+
- test/commands/package_command_spec.rb
|
108
|
+
- test/helper.rb
|
109
|
+
homepage: https://github.com/mttkay/replicant
|
110
|
+
licenses:
|
111
|
+
- MIT
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
require_paths:
|
115
|
+
- lib
|
116
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
117
|
+
none: false
|
118
|
+
requirements:
|
119
|
+
- - ! '>='
|
120
|
+
- !ruby/object:Gem::Version
|
121
|
+
version: '0'
|
122
|
+
segments:
|
123
|
+
- 0
|
124
|
+
hash: -4199582713992118202
|
125
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
126
|
+
none: false
|
127
|
+
requirements:
|
128
|
+
- - ! '>='
|
129
|
+
- !ruby/object:Gem::Version
|
130
|
+
version: '0'
|
131
|
+
requirements: []
|
132
|
+
rubyforge_project:
|
133
|
+
rubygems_version: 1.8.25
|
134
|
+
signing_key:
|
135
|
+
specification_version: 3
|
136
|
+
summary: A REPL for the Android Debug Bridge
|
137
|
+
test_files: []
|