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.
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ script: "bundle exec rake test"
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
@@ -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.
@@ -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
+ ![repl](https://raw.github.com/mttkay/replicant/master/screenshots/01_repl.png)
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
+ [![Build Status](https://travis-ci.org/mttkay/replicant.png)](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/
@@ -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
+
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ begin
3
+ require 'replicant'
4
+ rescue LoadError
5
+ begin
6
+ require 'rubygems'
7
+ require 'replicant'
8
+ end
9
+ end
10
+
11
+ Replicant::REPL.new.run
@@ -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,3 @@
1
+ module Replicant
2
+ VERSION = "0.0.1"
3
+ 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
@@ -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: []