danger-apkstats 0.0.2 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +19 -1
- data/.travis.yml +18 -5
- data/Dangerfile.sample +15 -0
- data/Gemfile +3 -1
- data/Gemfile.lock +136 -0
- data/Guardfile +4 -2
- data/README.md +25 -4
- data/Rakefile +12 -10
- data/danger-apkstats.gemspec +20 -19
- data/lib/apkstats/command/apk_analyzer.rb +56 -21
- data/lib/apkstats/command/executable.rb +62 -0
- data/lib/apkstats/entity/apk_info.rb +40 -0
- data/lib/apkstats/entity/apk_info_diff.rb +67 -0
- data/lib/apkstats/entity/feature.rb +100 -0
- data/lib/apkstats/entity/permission.rb +86 -0
- data/lib/apkstats/gem_version.rb +3 -1
- data/lib/apkstats/helper/bytes.rb +65 -0
- data/lib/apkstats/plugin.rb +199 -38
- data/lib/danger_apkstats.rb +2 -0
- data/lib/danger_plugin.rb +2 -0
- data/spec/apkstats_spec.rb +4 -24
- data/spec/command/apk_analyzer_spec.rb +130 -0
- data/spec/entity/apk_info_diff_spec.rb +110 -0
- data/spec/entity/apk_info_spec.rb +49 -0
- data/spec/entity/features_spec.rb +42 -0
- data/spec/entity/permissions_spec.rb +37 -0
- data/spec/fixture/app-base.apk +0 -0
- data/spec/fixture/app-other1.apk +0 -0
- data/spec/fixture/app-other2.apk +0 -0
- data/spec/fixture/app-other3.apk +0 -0
- data/spec/fixture/app-other4.apk +0 -0
- data/spec/fixture/app-other5.apk +0 -0
- data/spec/fixture/github_pr.json +345 -0
- data/spec/helper/bytes_spec.rb +95 -0
- data/spec/spec_helper.rb +10 -2
- data/spec/stub/command.rb +20 -0
- metadata +38 -3
- data/lib/apkstats/command/executable_command.rb +0 -24
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apkstats::Command
|
4
|
+
module Executable
|
5
|
+
require "open3"
|
6
|
+
|
7
|
+
attr_reader :command_path
|
8
|
+
|
9
|
+
def executable?
|
10
|
+
File.executable?(command_path)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Compare two apk files and return results.
|
14
|
+
#
|
15
|
+
# {
|
16
|
+
# base: {
|
17
|
+
# file_size: Integer,
|
18
|
+
# download_size: Integer,
|
19
|
+
# required_features: Array<String>,
|
20
|
+
# non_required_features: Array<String>,
|
21
|
+
# permissions: Array<String>,
|
22
|
+
# min_sdk: String,
|
23
|
+
# target_sdk: String,
|
24
|
+
# },
|
25
|
+
# other: {
|
26
|
+
# file_size: Integer,
|
27
|
+
# download_size: Integer,
|
28
|
+
# required_features: Array<String>,
|
29
|
+
# non_required_features: Array<String>,
|
30
|
+
# permissions: Array<String>,
|
31
|
+
# min_sdk: String,
|
32
|
+
# target_sdk: String,
|
33
|
+
# },
|
34
|
+
# diff: {
|
35
|
+
# file_size: Integer,
|
36
|
+
# download_size: Integer,
|
37
|
+
# required_features: {
|
38
|
+
# new: Array<String>,
|
39
|
+
# removed: Array<String>,
|
40
|
+
# },
|
41
|
+
# non_required_features:{
|
42
|
+
# new: Array<String>,
|
43
|
+
# removed: Array<String>,
|
44
|
+
# },
|
45
|
+
# permissions: {
|
46
|
+
# new: Array<String>,
|
47
|
+
# removed: Array<String>,
|
48
|
+
# },
|
49
|
+
# min_sdk: Array<String>,
|
50
|
+
# target_sdk: Array<String>,
|
51
|
+
# }
|
52
|
+
# }
|
53
|
+
#
|
54
|
+
# @return [Hash]
|
55
|
+
def compare_with(apk_filepath, other_apk_filepath)
|
56
|
+
base = Apkstats::Entity::ApkInfo.new(self, apk_filepath)
|
57
|
+
other = Apkstats::Entity::ApkInfo.new(self, other_apk_filepath)
|
58
|
+
|
59
|
+
Apkstats::Entity::ApkInfoDiff.new(base, other).to_h
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apkstats::Entity
|
4
|
+
class ApkInfo
|
5
|
+
KEYS = %i(
|
6
|
+
file_size
|
7
|
+
download_size
|
8
|
+
required_features
|
9
|
+
non_required_features
|
10
|
+
permissions
|
11
|
+
min_sdk
|
12
|
+
target_sdk
|
13
|
+
).freeze
|
14
|
+
|
15
|
+
# Integer
|
16
|
+
attr_accessor :file_size, :download_size
|
17
|
+
|
18
|
+
# String
|
19
|
+
attr_accessor :min_sdk, :target_sdk
|
20
|
+
|
21
|
+
# Array<String>
|
22
|
+
attr_accessor :required_features, :non_required_features, :permissions
|
23
|
+
|
24
|
+
def initialize(command, apk_filepath)
|
25
|
+
KEYS.each do |key|
|
26
|
+
self.send("#{key}=", command.send(key, apk_filepath))
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def [](key)
|
31
|
+
send(key)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_h
|
35
|
+
KEYS.each_with_object({}) do |key, acc|
|
36
|
+
acc[key] = self[key]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apkstats::Entity
|
4
|
+
class ApkInfoDiff
|
5
|
+
KEYS = Apkstats::Entity::ApkInfo::KEYS
|
6
|
+
|
7
|
+
# ApkInfo
|
8
|
+
attr_reader :base, :other
|
9
|
+
|
10
|
+
private(:base, :other)
|
11
|
+
|
12
|
+
def initialize(base, other)
|
13
|
+
@base = base
|
14
|
+
@other = other
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_h
|
18
|
+
KEYS.each_with_object({}) do |key, acc|
|
19
|
+
acc[key] = self.send(key)
|
20
|
+
end.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
def file_size
|
24
|
+
# Integer
|
25
|
+
@base[__method__].to_i - @other[__method__].to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
def download_size
|
29
|
+
# Integer
|
30
|
+
@base[__method__].to_i - @other[__method__].to_i
|
31
|
+
end
|
32
|
+
|
33
|
+
def required_features
|
34
|
+
# Features
|
35
|
+
{
|
36
|
+
new: (@base[__method__] - @other[__method__]).to_a,
|
37
|
+
removed: (@other[__method__] - @base[__method__]).to_a,
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def non_required_features
|
42
|
+
# Features
|
43
|
+
{
|
44
|
+
new: (@base[__method__] - @other[__method__]).to_a,
|
45
|
+
removed: (@other[__method__] - @base[__method__]).to_a,
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def permissions
|
50
|
+
# Permissions
|
51
|
+
{
|
52
|
+
new: (@base[__method__] - @other[__method__]).to_a,
|
53
|
+
removed: (@other[__method__] - @base[__method__]).to_a,
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
def min_sdk
|
58
|
+
# String
|
59
|
+
[@base[__method__], @other[__method__]].uniq
|
60
|
+
end
|
61
|
+
|
62
|
+
def target_sdk
|
63
|
+
# String
|
64
|
+
[@base[__method__], @other[__method__]].uniq
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apkstats::Entity
|
4
|
+
class Feature
|
5
|
+
# String
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
# String?
|
9
|
+
attr_reader :implied_reason
|
10
|
+
|
11
|
+
def initialize(name, not_required: false, implied_reason: nil)
|
12
|
+
@name = name
|
13
|
+
# cast to Boolean
|
14
|
+
@not_required = not_required == true
|
15
|
+
@implied_reason = implied_reason || nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def not_required?
|
19
|
+
@not_required
|
20
|
+
end
|
21
|
+
|
22
|
+
def implied?
|
23
|
+
@implied_reason
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
if implied?
|
28
|
+
"#{name} (#{implied_reason})"
|
29
|
+
elsif not_required?
|
30
|
+
"#{name} (not-required)"
|
31
|
+
else
|
32
|
+
name
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def ==(other)
|
37
|
+
return if !other || other.class != self.class
|
38
|
+
|
39
|
+
to_s == other.to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
def eql?(other)
|
43
|
+
to_s.eql?(other.to_s)
|
44
|
+
end
|
45
|
+
|
46
|
+
def hash
|
47
|
+
h = not_required? ? 1 : 0
|
48
|
+
h *= 31
|
49
|
+
h += name.hash
|
50
|
+
|
51
|
+
if implied_reason
|
52
|
+
h *= 31
|
53
|
+
h += implied_reason.hash
|
54
|
+
end
|
55
|
+
|
56
|
+
h
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
class Features
|
61
|
+
attr_reader :values
|
62
|
+
|
63
|
+
# Array<Feature>
|
64
|
+
def initialize(feature_arr)
|
65
|
+
@values = feature_arr
|
66
|
+
end
|
67
|
+
|
68
|
+
def -(other)
|
69
|
+
raise "#{self.class} cannot handle #{other.class} with the minus operator" unless other.class == Features
|
70
|
+
|
71
|
+
self_hash = Features.hashnize(self)
|
72
|
+
other_hash = Features.hashnize(other)
|
73
|
+
|
74
|
+
diff_features = (self_hash.keys - other_hash.keys).map do |key|
|
75
|
+
self_hash[key]
|
76
|
+
end
|
77
|
+
|
78
|
+
Features.new(diff_features)
|
79
|
+
end
|
80
|
+
|
81
|
+
def to_a
|
82
|
+
values.map(&:to_s)
|
83
|
+
end
|
84
|
+
|
85
|
+
def eql?(other)
|
86
|
+
return if !other || other.class == Features
|
87
|
+
other.values == values
|
88
|
+
end
|
89
|
+
|
90
|
+
def hash
|
91
|
+
values.hash
|
92
|
+
end
|
93
|
+
|
94
|
+
def self.hashnize(features)
|
95
|
+
features.values.each_with_object({}) do |feature, acc|
|
96
|
+
acc[[feature.name, feature.not_required?]] = feature
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apkstats::Entity
|
4
|
+
class Permission
|
5
|
+
# String
|
6
|
+
attr_reader :name
|
7
|
+
|
8
|
+
# String?
|
9
|
+
attr_reader :max_sdk
|
10
|
+
|
11
|
+
def initialize(name, max_sdk: nil)
|
12
|
+
@name = name
|
13
|
+
@max_sdk = max_sdk
|
14
|
+
end
|
15
|
+
|
16
|
+
def to_s
|
17
|
+
if max_sdk
|
18
|
+
"#{name} maxSdkVersion=#{max_sdk}"
|
19
|
+
else
|
20
|
+
name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
return if !other || other.class != self.class
|
26
|
+
|
27
|
+
to_s == other.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def eql?(other)
|
31
|
+
to_s.eql?(other.to_s)
|
32
|
+
end
|
33
|
+
|
34
|
+
def hash
|
35
|
+
h = name.hash
|
36
|
+
|
37
|
+
if max_sdk
|
38
|
+
h *= 31
|
39
|
+
h += max_sdk.hash
|
40
|
+
end
|
41
|
+
|
42
|
+
h
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Permissions
|
47
|
+
attr_reader :values
|
48
|
+
|
49
|
+
# Array<Permission>
|
50
|
+
def initialize(permission_arr)
|
51
|
+
@values = permission_arr
|
52
|
+
end
|
53
|
+
|
54
|
+
def -(other)
|
55
|
+
raise "#{self.class} cannot handle #{other.class} with the minus operator" unless other.class == Permissions
|
56
|
+
|
57
|
+
self_hash = Permissions.hashnize(self)
|
58
|
+
other_hash = Permissions.hashnize(other)
|
59
|
+
|
60
|
+
diff_permissions = (self_hash.keys - other_hash.keys).map do |key|
|
61
|
+
self_hash[key]
|
62
|
+
end
|
63
|
+
|
64
|
+
Permissions.new(diff_permissions)
|
65
|
+
end
|
66
|
+
|
67
|
+
def to_a
|
68
|
+
values.map(&:to_s)
|
69
|
+
end
|
70
|
+
|
71
|
+
def eql?(other)
|
72
|
+
return if !other || other.class == Permissions
|
73
|
+
other.values == values
|
74
|
+
end
|
75
|
+
|
76
|
+
def hash
|
77
|
+
values.hash
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.hashnize(permissions)
|
81
|
+
permissions.values.each_with_object({}) do |permission, acc|
|
82
|
+
acc[[permission.name, permission.max_sdk]] = permission
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/lib/apkstats/gem_version.rb
CHANGED
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apkstats::Helper
|
4
|
+
module Bytes
|
5
|
+
STEP = 2**10.to_f
|
6
|
+
|
7
|
+
def self.from_b(byte)
|
8
|
+
Byte.new(byte)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.from_kb(k_byte)
|
12
|
+
Byte.new(down_unit(k_byte))
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.from_mb(m_byte)
|
16
|
+
Byte.new(down_unit(down_unit(m_byte)))
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.up_unit(size)
|
20
|
+
(size.to_f / STEP).round(2)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.down_unit(size)
|
24
|
+
size.to_f * STEP
|
25
|
+
end
|
26
|
+
|
27
|
+
class Byte
|
28
|
+
attr_reader :value
|
29
|
+
|
30
|
+
def initialize(value)
|
31
|
+
@value = value
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_b
|
35
|
+
value
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_kb
|
39
|
+
Bytes.up_unit(value)
|
40
|
+
end
|
41
|
+
|
42
|
+
def to_mb
|
43
|
+
Bytes.up_unit(to_kb)
|
44
|
+
end
|
45
|
+
|
46
|
+
def to_s_b
|
47
|
+
add_op(to_b)
|
48
|
+
end
|
49
|
+
|
50
|
+
def to_s_kb
|
51
|
+
add_op(to_kb)
|
52
|
+
end
|
53
|
+
|
54
|
+
def to_s_mb
|
55
|
+
add_op(to_mb)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def add_op(size)
|
61
|
+
size.negative? ? size.to_s : "+#{size}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/apkstats/plugin.rb
CHANGED
@@ -1,80 +1,241 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "helper/bytes"
|
4
|
+
|
5
|
+
require_relative "entity/apk_info"
|
6
|
+
require_relative "entity/apk_info_diff"
|
7
|
+
require_relative "entity/feature"
|
8
|
+
require_relative "entity/permission"
|
9
|
+
|
10
|
+
require_relative "command/executable"
|
11
|
+
require_relative "command/apk_analyzer"
|
12
|
+
|
1
13
|
module Danger
|
2
|
-
#
|
3
|
-
#
|
14
|
+
# Show stats of your apk file.
|
15
|
+
# By default, it's done using apkanalyzer in android sdk.
|
16
|
+
#
|
17
|
+
# All command need your apk filepath like below
|
18
|
+
#
|
19
|
+
# apkstats.apk_filepath=<your new apk filepath>
|
20
|
+
#
|
21
|
+
# @example Compare two apk files and print it.
|
22
|
+
#
|
23
|
+
# apkstats.compare_with(<your old apk filepath>, do_report: true) # report it in markdown table
|
24
|
+
# apkstats.compare_with(<your old apk filepath>, do_report: false) # just return results
|
25
|
+
#
|
26
|
+
# @example Show the file size of your apk file.
|
27
|
+
#
|
28
|
+
# apkstats.file_size
|
29
|
+
#
|
30
|
+
# @example Show the download size of your apk file.
|
31
|
+
#
|
32
|
+
# apkstats.download_size
|
33
|
+
#
|
34
|
+
# @example Show all required features of your apk file.
|
35
|
+
#
|
36
|
+
# apkstats.required_features
|
37
|
+
#
|
38
|
+
# @example Show all non-required features of your apk file.
|
39
|
+
#
|
40
|
+
# apkstats.non_required_features
|
4
41
|
#
|
5
|
-
#
|
6
|
-
# the public interface documented. Danger uses [YARD](http://yardoc.org/)
|
7
|
-
# for generating documentation from your plugin source, and you can verify
|
8
|
-
# by running `danger plugins lint` or `bundle exec rake spec`.
|
42
|
+
# @example Show all requested permissions of your apk file.
|
9
43
|
#
|
10
|
-
#
|
44
|
+
# apkstats.permissions
|
11
45
|
#
|
12
|
-
# @example
|
46
|
+
# @example Show the min sdk version of your apk file.
|
13
47
|
#
|
14
|
-
#
|
48
|
+
# apkstats.min_sdk
|
49
|
+
#
|
50
|
+
# @example Show the target sdk version of your apk file.
|
51
|
+
#
|
52
|
+
# apkstats.target_sdk
|
15
53
|
#
|
16
54
|
# @see Jumpei Matsuda/danger-apkstats
|
17
55
|
# @tags android, apk_stats
|
18
56
|
#
|
19
57
|
class DangerApkstats < Plugin
|
20
|
-
require_relative 'command/executable_command'
|
21
|
-
require_relative 'command/apk_analyzer'
|
22
|
-
|
23
58
|
COMMAND_TYPE_MAP = {
|
24
|
-
apk_analyzer:
|
59
|
+
apk_analyzer: Apkstats::Command::ApkAnalyzer,
|
25
60
|
}.freeze
|
26
61
|
|
27
62
|
private_constant(:COMMAND_TYPE_MAP)
|
28
63
|
|
29
|
-
#
|
64
|
+
# *Optional*
|
65
|
+
# A command type to be run.
|
66
|
+
# One of keys of COMMAND_TYPE_MAP
|
30
67
|
#
|
31
|
-
# @return [Symbol,
|
68
|
+
# @return [Symbol, Nil] _
|
32
69
|
attr_accessor :command_type
|
33
70
|
|
34
|
-
#
|
71
|
+
# *Optional*
|
72
|
+
# A custom command path
|
73
|
+
#
|
74
|
+
# @return [Symbol, Nil] _
|
75
|
+
attr_accessor :command_path
|
76
|
+
|
77
|
+
# *Required*
|
78
|
+
# Your target apk filepath.
|
35
79
|
#
|
36
80
|
# @return [String]
|
37
81
|
attr_accessor :apk_filepath
|
38
82
|
|
39
|
-
#
|
83
|
+
# rubocop:disable Metrics/AbcSize
|
84
|
+
|
85
|
+
# Get stats of two apk files and calculate diffs between them.
|
86
|
+
#
|
87
|
+
# @param [String] other_apk_filepath your old apk
|
88
|
+
# @param [Boolean] do_report report markdown table if true, otherwise just return results
|
89
|
+
# @return [Hash] see command/executable#compare_with for more detail
|
90
|
+
def compare_with(other_apk_filepath, do_report: true)
|
91
|
+
raise "apk filepaths must be specified" if apk_filepath.nil? || apk_filepath.empty?
|
92
|
+
|
93
|
+
base_apk = Apkstats::Entity::ApkInfo.new(command, apk_filepath)
|
94
|
+
other_apk = Apkstats::Entity::ApkInfo.new(command, other_apk_filepath)
|
95
|
+
|
96
|
+
return {
|
97
|
+
base: base_apk.to_h,
|
98
|
+
other: base_apk.to_h,
|
99
|
+
diff: Apkstats::Entity::ApkInfoDiff.new(base_apk, other_apk).to_h,
|
100
|
+
}.tap do |result|
|
101
|
+
break unless do_report
|
102
|
+
|
103
|
+
diff = result[:diff]
|
104
|
+
|
105
|
+
md = +"### Apk comparision results" << "\n\n"
|
106
|
+
md << "Property | Summary" << "\n"
|
107
|
+
md << ":--- | :---" << "\n"
|
108
|
+
|
109
|
+
diff[:min_sdk].tap do |min_sdk|
|
110
|
+
break if min_sdk.size == 1
|
111
|
+
|
112
|
+
md << "Min SDK Change | Before #{min_sdk[1]} / After #{min_sdk[0]}" << "\n"
|
113
|
+
end
|
114
|
+
|
115
|
+
diff[:target_sdk].tap do |target_sdk|
|
116
|
+
break if target_sdk.size == 1
|
117
|
+
|
118
|
+
md << "Target SDK Change | Before #{target_sdk[1]} / After #{target_sdk[0]}" << "\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
result[:base][:file_size].tap do |file_size|
|
122
|
+
size = Apkstats::Helper::Bytes.from_b(file_size)
|
123
|
+
|
124
|
+
md << "New File Size | #{size.to_b} Bytes. (#{size.to_mb} MB " << "\n"
|
125
|
+
end
|
40
126
|
|
41
|
-
|
42
|
-
|
127
|
+
diff[:file_size].tap do |file_size|
|
128
|
+
size = Apkstats::Helper::Bytes.from_b(file_size)
|
43
129
|
|
44
|
-
|
130
|
+
md << "File Size Change | #{size.to_s_b} Bytes. (#{size.to_s_kb} KB) " << "\n"
|
131
|
+
end
|
132
|
+
|
133
|
+
diff[:download_size].tap do |download_size|
|
134
|
+
size = Apkstats::Helper::Bytes.from_b(download_size)
|
45
135
|
|
46
|
-
|
47
|
-
if out&.empty?
|
48
|
-
message("Apk file size was not changed")
|
49
|
-
elsif out
|
50
|
-
left, right, diff, = out.split("\s")
|
51
|
-
message("Apk file size was changed by #{diff} : from #{left} to #{right}")
|
52
|
-
else
|
53
|
-
warn(err)
|
136
|
+
md << "Download Size Change | #{size.to_s_b} Bytes. (#{size.to_s_kb} KB) " << "\n"
|
54
137
|
end
|
138
|
+
|
139
|
+
report_hash_and_arrays = lambda { |key, name|
|
140
|
+
list_up_entities = lambda { |type_key, label|
|
141
|
+
diff[key][type_key].tap do |features|
|
142
|
+
break if features.empty?
|
143
|
+
|
144
|
+
md << "#{label} | " << features.map { |f| "- #{f}" }.join("<br>").to_s << "\n"
|
145
|
+
end
|
146
|
+
}
|
147
|
+
|
148
|
+
list_up_entities.call(:new, "New #{name}")
|
149
|
+
list_up_entities.call(:removed, "Removed #{name}")
|
150
|
+
}
|
151
|
+
|
152
|
+
report_hash_and_arrays.call(:required_features, "Required Features")
|
153
|
+
report_hash_and_arrays.call(:non_required_features, "Non-required Features")
|
154
|
+
report_hash_and_arrays.call(:permissions, "Permissions")
|
155
|
+
|
156
|
+
markdown(md)
|
55
157
|
end
|
158
|
+
rescue StandardError => e
|
159
|
+
warn("apkstats failed to execute the command due to #{e.message}")
|
160
|
+
|
161
|
+
e.backtrace&.each { |line| STDOUT.puts line }
|
162
|
+
end
|
163
|
+
|
164
|
+
# rubocop:enable Metrics/AbcSize
|
165
|
+
|
166
|
+
# Show the file size of your apk file.
|
167
|
+
#
|
168
|
+
# @return [Fixnum] return positive value if exists, otherwise -1.
|
169
|
+
def file_size(_opts = {})
|
170
|
+
result = run_command(__method__)
|
171
|
+
result ? result.to_i : -1
|
172
|
+
end
|
173
|
+
|
174
|
+
# Show the download size of your apk file.
|
175
|
+
#
|
176
|
+
# @return [Fixnum] return positive value if exists, otherwise -1.
|
177
|
+
def download_size(_opts = {})
|
178
|
+
result = run_command(__method__)
|
179
|
+
result ? result.to_i : -1
|
180
|
+
end
|
56
181
|
|
57
|
-
|
182
|
+
# Show all required features of your apk file.
|
183
|
+
# The result doesn't contain non-required features.
|
184
|
+
#
|
185
|
+
# @return [Array<String>, Nil] return nil unless retrieved.
|
186
|
+
def required_features(_opts = {})
|
187
|
+
result = run_command(__method__)
|
188
|
+
result ? result.to_a : nil
|
58
189
|
end
|
59
190
|
|
60
|
-
|
61
|
-
|
191
|
+
# Show all non-required features of your apk file.
|
192
|
+
# The result doesn't contain required features.
|
193
|
+
#
|
194
|
+
# @return [Array<String>, Nil] return nil unless retrieved.
|
195
|
+
def non_required_features(_opts = {})
|
196
|
+
result = run_command(__method__)
|
197
|
+
result ? result.to_a : nil
|
198
|
+
end
|
62
199
|
|
63
|
-
|
64
|
-
|
200
|
+
# Show all permissions of your apk file.
|
201
|
+
#
|
202
|
+
# @return [Array<String>, Nil] return nil unless retrieved.
|
203
|
+
def permissions(_opts = {})
|
204
|
+
result = run_command(__method__)
|
205
|
+
result ? result.to_a : nil
|
65
206
|
end
|
66
207
|
|
67
|
-
|
68
|
-
|
208
|
+
# Show the min sdk version of your apk file.
|
209
|
+
#
|
210
|
+
# @return [String, Nil] return nil unless retrieved.
|
211
|
+
def min_sdk(_opts = {})
|
212
|
+
run_command(__method__)
|
213
|
+
end
|
69
214
|
|
70
|
-
|
71
|
-
|
215
|
+
# Show the target sdk version of your apk file.
|
216
|
+
#
|
217
|
+
# @return [String, Nil] return nil unless retrieved.
|
218
|
+
def target_sdk(_opts = {})
|
219
|
+
run_command(__method__)
|
72
220
|
end
|
73
221
|
|
74
222
|
private
|
75
223
|
|
224
|
+
def run_command(name)
|
225
|
+
raise "#{command.command_path} is not found or is not executable" unless command.executable?
|
226
|
+
|
227
|
+
return command.send(name, apk_filepath)
|
228
|
+
rescue StandardError => e
|
229
|
+
warn("apkstats failed to execute the command #{name} due to #{e.message}")
|
230
|
+
|
231
|
+
e.backtrace&.each { |line| puts line }
|
232
|
+
|
233
|
+
nil
|
234
|
+
end
|
235
|
+
|
76
236
|
def command
|
77
|
-
|
237
|
+
command_type ||= :apk_analyzer
|
238
|
+
@command ||= COMMAND_TYPE_MAP[command_type.to_sym].new(command_path: command_path)
|
78
239
|
end
|
79
240
|
end
|
80
241
|
end
|