replicant-adb 0.0.1 → 1.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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 25ff78db9805a61866c8ba3be4c9d404ca666858
4
+ data.tar.gz: 44c65677a410f9bf4b78c3b8d44ccf97ce6c00bd
5
+ SHA512:
6
+ metadata.gz: 636cc5f837bb35dc349c5af94d908368ee9ea8a23172d2ab525ec32e5495000ca190305d864fa83df12f770afaa0a5c57ff9a58be4a2f1389e53de7089fe25fe
7
+ data.tar.gz: 9acc12a4f28448766545489786faf6ed5b0dc527560dd347bd4b616125fc2ebe723ba84e866aed4993dd81758f6482bfc2d44d756baf293e12e7bd8959ac3101
data/README.md CHANGED
@@ -1,5 +1,7 @@
1
1
  replicant - a repl for adb
2
2
  ==========================
3
+ [![Gem Version](https://badge.fury.io/rb/replicant-adb.png)](http://badge.fury.io/rb/replicant-adb)
4
+ [![Build Status](https://travis-ci.org/mttkay/replicant.png)](https://travis-ci.org/mttkay/replicant)
3
5
 
4
6
  `replicant` is an interactive shell (a [REPL][2]) for `adb`, the Android Debug Bridge.
5
7
  It is partially based on Chris Wanstrath's excellent [repl][0] command line wrapper.
@@ -23,7 +25,7 @@ From here on we list the available commands, fix the device to the first listed
23
25
  emulator, uninstall the app, then reset the session.
24
26
 
25
27
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
26
- v1.0.0
28
+ v0.0.1
27
29
  dP oo dP
28
30
  88 88
29
31
  88d888b. .d8888b. 88d888b. 88 dP .d8888b. .d8888b. 88d888b. d8888P
@@ -32,45 +34,34 @@ emulator, uninstall the app, then reset the session.
32
34
  dP `88888P' 88Y888P' dP dP `88888P' `88888P8 dP dP dP
33
35
  88
34
36
  dP (c) 2013 Matthias Kaeppler
35
-
36
-
37
- Type !list to see a list of commands.
37
+
38
+
39
+ Type '!' to see a list of commands, '?' for environment info.
38
40
  Commands not starting in '!' are sent to adb verbatim.
39
41
  Use Ctrl-D (i.e. EOF) to exit.
40
42
  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
41
43
  Setting default package to "com.soundcloud.android"
42
- -- com.soundcloud.android, No device set
43
- >> !list
44
+ >> !
45
+ !clear -- clear application data
44
46
  !device -- set a default device to work with
45
47
  !devices -- print a list of connected devices
46
- !list -- print a list of available commands
48
+ !logcat -- access device logs
47
49
  !package -- set a default package to work with
48
50
  !reset -- clear current device and package
49
51
  !restart -- restart ADB
50
52
  OK.
51
- -- com.soundcloud.android, No device set
52
53
  >> !devices
53
-
54
- 005de387d71505d6 [Nexus 4]
55
- emulator-5554 [Android SDK built for x86]
56
-
54
+
55
+ [0] Nexus 4 | 005de387d71505d6
56
+ [1] Genymotion Nexus 4 API 18 768x1280 | 192.168.56.101:5555
57
+
57
58
  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]
59
+ >> !device 0
60
+ Setting default device to 005de387d71505d6 [Nexus 4]
65
61
  OK.
66
- -- com.soundcloud.android, emulator-5554 [Android SDK built for x86]
67
62
  >> uninstall
68
63
  Success
69
64
  OK.
70
- -- com.soundcloud.android, emulator-5554 [Android SDK built for x86]
71
- >> !reset
72
- OK.
73
- -- No package set, No device set
74
65
  >>
75
66
 
76
67
  Install
@@ -81,22 +72,15 @@ command history and tab-completion, although it's not a requirement.
81
72
  `replicant` integrates with `rlwrap` automatically;
82
73
  it's sufficient for it to just be installed.
83
74
 
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
75
+ If all requirements are met, you can install `replicant` as a Ruby gem:
92
76
 
77
+ $ gem install replicant-adb
93
78
 
94
79
  Contributing
95
80
  ------------
96
81
 
97
- [![Build Status](https://travis-ci.org/mttkay/replicant.png)](https://travis-ci.org/mttkay/replicant)
98
-
99
82
  Please hack on replicant and make it better and more feature complete!
83
+ Here's a general list of guidelines you should follow:
100
84
 
101
85
  * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
102
86
  * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
@@ -105,6 +89,20 @@ Please hack on replicant and make it better and more feature complete!
105
89
  * Write an executable spec. See existing specs in the test/ folder for examples.
106
90
  * Commit and push until you are happy with your contribution.
107
91
 
92
+ After checking out the project, change into the project dir and do
93
+
94
+ $ bundle install
95
+
96
+ to make sure all dependencies are installed. After making local changes, you can
97
+ install locally using
98
+
99
+ $ rake install
100
+
101
+ And don't forget to regularly
102
+
103
+ $ rake test
104
+
105
+
108
106
  Copyright
109
107
  ---------
110
108
 
@@ -3,7 +3,9 @@ require 'active_support/core_ext/string/filters.rb'
3
3
  require 'active_support/core_ext/object/try.rb'
4
4
 
5
5
  require 'replicant/version'
6
- require 'replicant/command'
7
6
  require 'replicant/styles'
7
+ require 'replicant/process_muncher'
8
+ require 'replicant/log_muncher'
9
+ require 'replicant/commands'
8
10
  require 'replicant/device'
9
11
  require 'replicant/repl'
@@ -0,0 +1,10 @@
1
+ require_relative "commands/command"
2
+ require_relative "commands/adb_command"
3
+ require_relative "commands/clear_command"
4
+ require_relative "commands/device_command"
5
+ require_relative "commands/devices_command"
6
+ require_relative "commands/env_command"
7
+ require_relative "commands/list_command"
8
+ require_relative "commands/package_command"
9
+ require_relative "commands/reset_command"
10
+ require_relative "commands/restart_command"
@@ -0,0 +1,47 @@
1
+ class AdbCommand < Command
2
+
3
+ class Result
4
+ attr_accessor :pid, :code, :output
5
+
6
+ def to_s
7
+ "result: pid=#{pid} code=#{code} output=#{output}"
8
+ end
9
+ end
10
+
11
+ def run
12
+ Result.new.tap do |result|
13
+ cmd = "#{command}"
14
+
15
+ putsd cmd
16
+
17
+ if interactive?
18
+ system cmd
19
+ else
20
+ result.output = `#{cmd}`
21
+ output result.output
22
+ end
23
+ result.pid = $?.pid
24
+ result.code = $?.exitstatus
25
+ putsd "Command returned with exit status #{result.code}"
26
+ end
27
+ end
28
+
29
+ def command
30
+ adb = "adb"
31
+ adb << " -s #{@repl.default_device.id}" if @repl.default_device
32
+ adb << " #{args}"
33
+ adb << " #{@repl.default_package}" if @repl.default_package && package_dependent?
34
+ adb << " 2>&1" # redirect stderr to stdout so that we can silence it
35
+ adb
36
+ end
37
+
38
+ private
39
+
40
+ def interactive?
41
+ args == "shell" || args.start_with?("logcat")
42
+ end
43
+
44
+ def package_dependent?
45
+ ["uninstall"].include?(args)
46
+ end
47
+ end
@@ -0,0 +1,23 @@
1
+ class ClearCommand < Command
2
+
3
+ def description
4
+ "clear application data"
5
+ end
6
+
7
+ # TODO: this is not a very good argument validator
8
+ def valid_args?
9
+ args.present? || @repl.default_package
10
+ end
11
+
12
+ def usage
13
+ "#{name} [com.example.package|<empty>(when default package is set)]"
14
+ end
15
+
16
+ def run
17
+ package = args.present? ? args : @repl.default_package
18
+ # Clear app data - cache, SharedPreferences, Databases
19
+ AdbCommand.new(@repl, "shell su -c \"rm -r /data/data/#{package}/*\"").execute
20
+ # Force application stop to recreate shared preferences, databases with new launch
21
+ AdbCommand.new(@repl, "shell am force-stop #{package}").execute
22
+ end
23
+ end
@@ -0,0 +1,85 @@
1
+ require 'stringio'
2
+
3
+ class Command
4
+
5
+ include Styles
6
+
7
+ def self.inherited(subclass)
8
+ @@subclasses ||= []
9
+ @@subclasses << subclass
10
+ end
11
+
12
+ def self.all
13
+ (@@subclasses - [AdbCommand, ListCommand, EnvCommand]).map do |clazz|
14
+ clazz.new(nil)
15
+ end
16
+ end
17
+
18
+ def self.load(repl, command_line)
19
+ if command_line == '!'
20
+ # load command that lists available commands
21
+ ListCommand.new(repl)
22
+ elsif command_line == '?'
23
+ EnvCommand.new(repl)
24
+ elsif command_line.start_with?('!')
25
+ # load custom command
26
+ command_parts = command_line[1..-1].split
27
+ command_name = command_parts.first
28
+ command_args = command_parts[1..-1].join(' ')
29
+ command_class = "#{command_name.capitalize}Command"
30
+ begin
31
+ clazz = Object.const_get(command_class)
32
+ clazz.new(repl, command_args)
33
+ rescue NameError => e
34
+ nil
35
+ end
36
+ else
37
+ # forward command to ADB
38
+ AdbCommand.new(repl, command_line.strip)
39
+ end
40
+ end
41
+
42
+ attr_reader :args
43
+
44
+ def initialize(repl, args = nil, options = {})
45
+ @repl = repl
46
+ @args = args.strip if args
47
+ @options = options
48
+ end
49
+
50
+ def name
51
+ "!#{self.class.name.gsub("Command", "").downcase}"
52
+ end
53
+
54
+ # subclasses override this to provide a description of their functionality
55
+ def description
56
+ "TODO: description missing"
57
+ end
58
+
59
+ # subclasses override this to provide a usage example
60
+ def usage
61
+ end
62
+
63
+ def execute
64
+ if valid_args?
65
+ run
66
+ else
67
+ output "Invalid arguments. Ex.: #{usage}"
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def valid_args?
74
+ true
75
+ end
76
+
77
+ def output(message)
78
+ @repl.output(message) unless @options[:silent]
79
+ end
80
+
81
+ def putsd(message)
82
+ puts "[DEBUG] #{message}" if @repl.debug?
83
+ end
84
+
85
+ end
@@ -0,0 +1,87 @@
1
+ class DeviceCommand < Command
2
+
3
+ @@threads = []
4
+
5
+ def initialize(repl, args = nil, options = {})
6
+ super
7
+ @process_muncher = ProcessMuncher.new(repl)
8
+ @log_muncher = LogMuncher.new(repl)
9
+ end
10
+
11
+ def description
12
+ "set a default device to work with"
13
+ end
14
+
15
+ def usage
16
+ "#{name} [<index>|<device_id>]"
17
+ end
18
+
19
+ def valid_args?
20
+ args.present? && /\S+/ =~ args
21
+ end
22
+
23
+ def run
24
+ default_device = if index? && (1..devices.size).include?(args.to_i)
25
+ # user selected by index
26
+ devices[args.to_i - 1]
27
+ else
28
+ # user selected by device ID
29
+ devices.detect { |d| d.id == args }
30
+ end
31
+
32
+ if default_device
33
+ @repl.default_device = default_device
34
+ output "Default device set to #{default_device.name}"
35
+
36
+ # kill any existing threads
37
+ putsd "Found #{@@threads.size} zombie threads, killing..." unless @@threads.empty?
38
+ @@threads.select! { |t| t.exit }
39
+
40
+ @@threads << @log_muncher.munch_logs do |logfile|
41
+ observe_pid_changes(logfile)
42
+ end
43
+
44
+ output "Logs are available at #{LogMuncher::LOGFILE}"
45
+ else
46
+ output "No such device"
47
+ end
48
+
49
+ default_device
50
+ end
51
+
52
+ private
53
+
54
+ def index?
55
+ /^\d+$/ =~ args
56
+ end
57
+
58
+ def devices
59
+ @devices ||= DevicesCommand.new(@repl, nil, :silent => true).execute
60
+ end
61
+
62
+ def observe_pid_changes(logfile)
63
+ @@threads << @process_muncher.scan_pid do |new_pid|
64
+ @log_muncher.current_pid = new_pid
65
+ if new_pid
66
+ log_state_change!(logfile, "#{@repl.default_package} (pid = #{new_pid})")
67
+ else
68
+ log_state_change!(logfile, "<all>")
69
+ end
70
+ end
71
+ end
72
+
73
+ def log_message!(o, message)
74
+ o.puts "*" * Styles::CONSOLE_WIDTH
75
+ o.puts " #{message}"
76
+ o.puts "*" * Styles::CONSOLE_WIDTH
77
+ o.flush
78
+ end
79
+
80
+ def log_state_change!(o, change)
81
+ msg = "Detected change in device or target package\n"
82
+ msg << "-" * Styles::CONSOLE_WIDTH
83
+ msg << "\n --> device = #{@repl.default_device.name}"
84
+ msg << "\n --> process = #{change}"
85
+ log_message!(o, msg)
86
+ end
87
+ end
@@ -0,0 +1,57 @@
1
+ class DevicesCommand < Command
2
+ def description
3
+ "print a list of connected devices"
4
+ end
5
+
6
+ def run
7
+ adb = AdbCommand.new(@repl, "devices -l", :silent => true)
8
+ device_lines = adb.execute.output.lines.to_a.reject do |line|
9
+ line.strip.empty? || line.include?("daemon") || line.include?("List of devices")
10
+ end
11
+
12
+ device_lines.reject! { |l| l =~ /offline/ }
13
+ device_ids = device_lines.map { |l| /([\S]+)\s+device/.match(l)[1] }
14
+ device_products = device_lines.map { |l| /product:([\S]+)/.match(l).try(:[], 1) }
15
+
16
+ device_names = device_lines.zip(device_ids).map do |l, id|
17
+ /model:([\S]+)/.match(l).try(:[], 1) || detect_device_name(id)
18
+ end
19
+
20
+ device_indices = (1..device_ids.size).to_a
21
+ devices = device_indices.zip(device_ids, device_names, device_products).map do |idx, id, name, product|
22
+ Device.new(idx, id, humanize_name(name, product))
23
+ end
24
+
25
+ output ""
26
+ output devices_string(devices)
27
+ output ""
28
+ devices
29
+ end
30
+
31
+ private
32
+
33
+ def detect_device_name(id)
34
+ if id.start_with?("emulator-")
35
+ "Android emulator"
36
+ else
37
+ "Unknown device"
38
+ end
39
+ end
40
+
41
+ def humanize_name(name_string, product)
42
+ if product == "vbox86p"
43
+ "Genymotion " + name_string.gsub(/___[\d_]+___/, "_")
44
+ else
45
+ name_string
46
+ end.gsub('_', ' ').squish
47
+ end
48
+
49
+ def devices_string(devices)
50
+ device_string = if devices.any?
51
+ padding = devices.map { |d| d.name.length }.max
52
+ devices.map { |d| "[#{d.idx}] #{d.name}#{' ' * (padding - d.name.length)} | #{d.id}" }.join("\n")
53
+ else
54
+ "No devices found"
55
+ end
56
+ end
57
+ end