app-info 2.5.1 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +9 -0
- data/.github/workflows/ci.yml +41 -0
- data/.rubocop.yml +26 -7
- data/CHANGELOG.md +43 -2
- data/Gemfile +6 -0
- data/README.md +110 -50
- data/app_info.gemspec +6 -3
- data/exe/app-info +12 -0
- data/lib/app_info/apk.rb +17 -22
- data/lib/app_info/core_ext/object/try.rb +77 -79
- data/lib/app_info/dsym.rb +3 -4
- data/lib/app_info/info_plist.rb +143 -0
- data/lib/app_info/ipa/framework.rb +3 -3
- data/lib/app_info/ipa.rb +72 -22
- data/lib/app_info/macos.rb +188 -0
- data/lib/app_info/{ipa/mobile_provision.rb → mobile_provision.rb} +8 -9
- data/lib/app_info/png_uncrush.rb +31 -26
- data/lib/app_info/proguard.rb +0 -1
- data/lib/app_info/shell.rb +34 -0
- data/lib/app_info/util.rb +46 -6
- data/lib/app_info/version.rb +1 -1
- data/lib/app_info.rb +21 -25
- metadata +51 -12
- data/.travis.yml +0 -9
- data/lib/app_info/ipa/info_plist.rb +0 -161
data/lib/app_info/apk.rb
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'ruby_apk'
|
4
4
|
require 'image_size'
|
5
5
|
require 'forwardable'
|
6
|
-
require 'app_info/util'
|
7
6
|
|
8
7
|
module AppInfo
|
9
8
|
# Parse APK file
|
@@ -24,8 +23,8 @@ module AppInfo
|
|
24
23
|
@file = file
|
25
24
|
end
|
26
25
|
|
27
|
-
def size(
|
28
|
-
AppInfo::Util.file_size(@file,
|
26
|
+
def size(human_size: false)
|
27
|
+
AppInfo::Util.file_size(@file, human_size)
|
29
28
|
end
|
30
29
|
|
31
30
|
def os
|
@@ -48,7 +47,7 @@ module AppInfo
|
|
48
47
|
alias build_version version_code
|
49
48
|
|
50
49
|
def name
|
51
|
-
resource.find('@string/app_name')
|
50
|
+
manifest.label || resource.find('@string/app_name')
|
52
51
|
end
|
53
52
|
|
54
53
|
def device_type
|
@@ -110,29 +109,23 @@ module AppInfo
|
|
110
109
|
end
|
111
110
|
|
112
111
|
def apk
|
113
|
-
Zip.warn_invalid_date = false # fix invaild date format warnings
|
114
|
-
|
115
112
|
@apk ||= ::Android::Apk.new(@file)
|
116
113
|
end
|
117
114
|
|
118
115
|
def icons
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
}
|
132
|
-
end
|
116
|
+
@icons ||= apk.icon.each_with_object([]) do |(path, data), obj|
|
117
|
+
icon_name = File.basename(path)
|
118
|
+
icon_path = File.join(contents, File.dirname(path))
|
119
|
+
icon_file = File.join(icon_path, icon_name)
|
120
|
+
FileUtils.mkdir_p icon_path
|
121
|
+
File.open(icon_file, 'wb') { |f| f.write(data) }
|
122
|
+
|
123
|
+
obj << {
|
124
|
+
name: icon_name,
|
125
|
+
file: icon_file,
|
126
|
+
dimensions: ImageSize.path(icon_file).size
|
127
|
+
}
|
133
128
|
end
|
134
|
-
|
135
|
-
@icons
|
136
129
|
end
|
137
130
|
|
138
131
|
def clear!
|
@@ -164,6 +157,7 @@ module AppInfo
|
|
164
157
|
# Android Certificate
|
165
158
|
class Certificate
|
166
159
|
attr_reader :path, :certificate
|
160
|
+
|
167
161
|
def initialize(path, certificate)
|
168
162
|
@path = path
|
169
163
|
@certificate = certificate
|
@@ -173,6 +167,7 @@ module AppInfo
|
|
173
167
|
# Android Sign
|
174
168
|
class Sign
|
175
169
|
attr_reader :path, :sign
|
170
|
+
|
176
171
|
def initialize(path, sign)
|
177
172
|
@path = path
|
178
173
|
@sign = sign
|
@@ -4,7 +4,71 @@
|
|
4
4
|
# Copy from https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/object/try.rb
|
5
5
|
|
6
6
|
module AppInfo
|
7
|
-
module Tryable
|
7
|
+
module Tryable # :nodoc:
|
8
|
+
##
|
9
|
+
# :method: try
|
10
|
+
#
|
11
|
+
# :call-seq:
|
12
|
+
# try(*a, &b)
|
13
|
+
#
|
14
|
+
# Invokes the public method whose name goes as first argument just like
|
15
|
+
# +public_send+ does, except that if the receiver does not respond to it the
|
16
|
+
# call returns +nil+ rather than raising an exception.
|
17
|
+
#
|
18
|
+
# This method is defined to be able to write
|
19
|
+
#
|
20
|
+
# @person.try(:name)
|
21
|
+
#
|
22
|
+
# instead of
|
23
|
+
#
|
24
|
+
# @person.name if @person
|
25
|
+
#
|
26
|
+
# +try+ calls can be chained:
|
27
|
+
#
|
28
|
+
# @person.try(:spouse).try(:name)
|
29
|
+
#
|
30
|
+
# instead of
|
31
|
+
#
|
32
|
+
# @person.spouse.name if @person && @person.spouse
|
33
|
+
#
|
34
|
+
# +try+ will also return +nil+ if the receiver does not respond to the method:
|
35
|
+
#
|
36
|
+
# @person.try(:non_existing_method) # => nil
|
37
|
+
#
|
38
|
+
# instead of
|
39
|
+
#
|
40
|
+
# @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
|
41
|
+
#
|
42
|
+
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
|
43
|
+
# to the method:
|
44
|
+
#
|
45
|
+
# nil.try(:to_i) # => nil, rather than 0
|
46
|
+
#
|
47
|
+
# Arguments and blocks are forwarded to the method if invoked:
|
48
|
+
#
|
49
|
+
# @posts.try(:each_slice, 2) do |a, b|
|
50
|
+
# ...
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# The number of arguments in the signature must match. If the object responds
|
54
|
+
# to the method the call is attempted and +ArgumentError+ is still raised
|
55
|
+
# in case of argument mismatch.
|
56
|
+
#
|
57
|
+
# If +try+ is called without arguments it yields the receiver to a given
|
58
|
+
# block unless it is +nil+:
|
59
|
+
#
|
60
|
+
# @person.try do |p|
|
61
|
+
# ...
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# You can also call try with a block without accepting an argument, and the block
|
65
|
+
# will be instance_eval'ed instead:
|
66
|
+
#
|
67
|
+
# @person.try { upcase.truncate(50) }
|
68
|
+
#
|
69
|
+
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
|
70
|
+
# with instances of classes that do not have +Object+ among their ancestors,
|
71
|
+
# like direct subclasses of +BasicObject+.
|
8
72
|
def try(method_name = nil, *args, &block)
|
9
73
|
if method_name.nil? && block_given?
|
10
74
|
if block.arity.zero?
|
@@ -17,6 +81,18 @@ module AppInfo
|
|
17
81
|
end
|
18
82
|
end
|
19
83
|
|
84
|
+
##
|
85
|
+
# :method: try!
|
86
|
+
#
|
87
|
+
# :call-seq:
|
88
|
+
# try!(*a, &b)
|
89
|
+
#
|
90
|
+
# Same as #try, but raises a +NoMethodError+ exception if the receiver is
|
91
|
+
# not +nil+ and does not implement the tried method.
|
92
|
+
#
|
93
|
+
# "a".try!(:upcase) # => "A"
|
94
|
+
# nil.try!(:upcase) # => nil
|
95
|
+
# 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer
|
20
96
|
def try!(method_name = nil, *args, &block)
|
21
97
|
if method_name.nil? && block_given?
|
22
98
|
if block.arity.zero?
|
@@ -33,82 +109,4 @@ end
|
|
33
109
|
|
34
110
|
class Object
|
35
111
|
include AppInfo::Tryable
|
36
|
-
|
37
|
-
##
|
38
|
-
# :method: try
|
39
|
-
#
|
40
|
-
# :call-seq:
|
41
|
-
# try(*a, &b)
|
42
|
-
#
|
43
|
-
# Invokes the public method whose name goes as first argument just like
|
44
|
-
# +public_send+ does, except that if the receiver does not respond to it the
|
45
|
-
# call returns +nil+ rather than raising an exception.
|
46
|
-
#
|
47
|
-
# This method is defined to be able to write
|
48
|
-
#
|
49
|
-
# @person.try(:name)
|
50
|
-
#
|
51
|
-
# instead of
|
52
|
-
#
|
53
|
-
# @person.name if @person
|
54
|
-
#
|
55
|
-
# +try+ calls can be chained:
|
56
|
-
#
|
57
|
-
# @person.try(:spouse).try(:name)
|
58
|
-
#
|
59
|
-
# instead of
|
60
|
-
#
|
61
|
-
# @person.spouse.name if @person && @person.spouse
|
62
|
-
#
|
63
|
-
# +try+ will also return +nil+ if the receiver does not respond to the method:
|
64
|
-
#
|
65
|
-
# @person.try(:non_existing_method) # => nil
|
66
|
-
#
|
67
|
-
# instead of
|
68
|
-
#
|
69
|
-
# @person.non_existing_method if @person.respond_to?(:non_existing_method) # => nil
|
70
|
-
#
|
71
|
-
# +try+ returns +nil+ when called on +nil+ regardless of whether it responds
|
72
|
-
# to the method:
|
73
|
-
#
|
74
|
-
# nil.try(:to_i) # => nil, rather than 0
|
75
|
-
#
|
76
|
-
# Arguments and blocks are forwarded to the method if invoked:
|
77
|
-
#
|
78
|
-
# @posts.try(:each_slice, 2) do |a, b|
|
79
|
-
# ...
|
80
|
-
# end
|
81
|
-
#
|
82
|
-
# The number of arguments in the signature must match. If the object responds
|
83
|
-
# to the method the call is attempted and +ArgumentError+ is still raised
|
84
|
-
# in case of argument mismatch.
|
85
|
-
#
|
86
|
-
# If +try+ is called without arguments it yields the receiver to a given
|
87
|
-
# block unless it is +nil+:
|
88
|
-
#
|
89
|
-
# @person.try do |p|
|
90
|
-
# ...
|
91
|
-
# end
|
92
|
-
#
|
93
|
-
# You can also call try with a block without accepting an argument, and the block
|
94
|
-
# will be instance_eval'ed instead:
|
95
|
-
#
|
96
|
-
# @person.try { upcase.truncate(50) }
|
97
|
-
#
|
98
|
-
# Please also note that +try+ is defined on +Object+. Therefore, it won't work
|
99
|
-
# with instances of classes that do not have +Object+ among their ancestors,
|
100
|
-
# like direct subclasses of +BasicObject+.
|
101
|
-
|
102
|
-
##
|
103
|
-
# :method: try!
|
104
|
-
#
|
105
|
-
# :call-seq:
|
106
|
-
# try!(*a, &b)
|
107
|
-
#
|
108
|
-
# Same as #try, but raises a +NoMethodError+ exception if the receiver is
|
109
|
-
# not +nil+ and does not implement the tried method.
|
110
|
-
#
|
111
|
-
# "a".try!(:upcase) # => "A"
|
112
|
-
# nil.try!(:upcase) # => nil
|
113
|
-
# 123.try!(:upcase) # => NoMethodError: undefined method `upcase' for 123:Integer
|
114
112
|
end
|
data/lib/app_info/dsym.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'macho'
|
4
|
-
require 'app_info/util'
|
5
4
|
|
6
5
|
module AppInfo
|
7
6
|
# DSYM parser
|
@@ -130,8 +129,8 @@ module AppInfo
|
|
130
129
|
@file.filetype
|
131
130
|
end
|
132
131
|
|
133
|
-
def size(
|
134
|
-
return Util.
|
132
|
+
def size(human_size: false)
|
133
|
+
return Util.size_to_human_size(@size) if human_size
|
135
134
|
|
136
135
|
@size
|
137
136
|
end
|
@@ -152,7 +151,7 @@ module AppInfo
|
|
152
151
|
cpu_name: cpu_name,
|
153
152
|
cpu_type: cpu_type,
|
154
153
|
size: size,
|
155
|
-
|
154
|
+
human_size: size(human_size: true)
|
156
155
|
}
|
157
156
|
end
|
158
157
|
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
require 'cfpropertylist'
|
5
|
+
require 'app_info/png_uncrush'
|
6
|
+
|
7
|
+
module AppInfo
|
8
|
+
# iOS Info.plist parser
|
9
|
+
class InfoPlist
|
10
|
+
extend Forwardable
|
11
|
+
|
12
|
+
def initialize(file)
|
13
|
+
@file = file
|
14
|
+
end
|
15
|
+
|
16
|
+
def version
|
17
|
+
release_version || build_version
|
18
|
+
end
|
19
|
+
|
20
|
+
def build_version
|
21
|
+
info.try(:[], 'CFBundleVersion')
|
22
|
+
end
|
23
|
+
|
24
|
+
def release_version
|
25
|
+
info.try(:[], 'CFBundleShortVersionString')
|
26
|
+
end
|
27
|
+
|
28
|
+
def identifier
|
29
|
+
info.try(:[], 'CFBundleIdentifier')
|
30
|
+
end
|
31
|
+
alias bundle_id identifier
|
32
|
+
|
33
|
+
def name
|
34
|
+
display_name || bundle_name
|
35
|
+
end
|
36
|
+
|
37
|
+
def display_name
|
38
|
+
info.try(:[], 'CFBundleDisplayName')
|
39
|
+
end
|
40
|
+
|
41
|
+
def bundle_name
|
42
|
+
info.try(:[], 'CFBundleName')
|
43
|
+
end
|
44
|
+
|
45
|
+
def min_os_version
|
46
|
+
min_sdk_version || min_system_version
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# Extract the Minimum OS Version from the Info.plist (iOS Only)
|
51
|
+
#
|
52
|
+
def min_sdk_version
|
53
|
+
info.try(:[], 'MinimumOSVersion')
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Extract the Minimum OS Version from the Info.plist (macOS Only)
|
58
|
+
#
|
59
|
+
def min_system_version
|
60
|
+
info.try(:[], 'LSMinimumSystemVersion')
|
61
|
+
end
|
62
|
+
|
63
|
+
def icons
|
64
|
+
@icons ||= ICON_KEYS[device_type]
|
65
|
+
end
|
66
|
+
|
67
|
+
def device_type
|
68
|
+
device_family = info.try(:[], 'UIDeviceFamily')
|
69
|
+
if device_family == [1]
|
70
|
+
AppInfo::Device::IPHONE
|
71
|
+
elsif device_family == [2]
|
72
|
+
AppInfo::Device::IPAD
|
73
|
+
elsif device_family == [1, 2]
|
74
|
+
AppInfo::Device::UNIVERSAL
|
75
|
+
elsif !info.try(:[], 'DTSDKName').nil? || !info.try(:[], 'DTPlatformName').nil?
|
76
|
+
AppInfo::Device::MACOS
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def iphone?
|
81
|
+
device_type == Device::IPHONE
|
82
|
+
end
|
83
|
+
|
84
|
+
def ipad?
|
85
|
+
device_type == Device::IPAD
|
86
|
+
end
|
87
|
+
|
88
|
+
def universal?
|
89
|
+
device_type == Device::UNIVERSAL
|
90
|
+
end
|
91
|
+
|
92
|
+
def macos?
|
93
|
+
device_type == Device::MACOS
|
94
|
+
end
|
95
|
+
|
96
|
+
def device_family
|
97
|
+
info.try(:[], 'UIDeviceFamily') || []
|
98
|
+
end
|
99
|
+
|
100
|
+
def release_type
|
101
|
+
if stored?
|
102
|
+
'Store'
|
103
|
+
else
|
104
|
+
build_type
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def [](key)
|
109
|
+
info.try(:[], key.to_s)
|
110
|
+
end
|
111
|
+
|
112
|
+
def_delegators :info, :to_h
|
113
|
+
|
114
|
+
def method_missing(method_name, *args, &block)
|
115
|
+
info.try(:[], Util.format_key(method_name)) ||
|
116
|
+
info.send(method_name) ||
|
117
|
+
super
|
118
|
+
end
|
119
|
+
|
120
|
+
def respond_to_missing?(method_name, *args)
|
121
|
+
info.key?(Util.format_key(method_name)) ||
|
122
|
+
info.respond_to?(method_name) ||
|
123
|
+
super
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
|
128
|
+
def info
|
129
|
+
return unless File.file?(@file)
|
130
|
+
|
131
|
+
@info ||= CFPropertyList.native_types(CFPropertyList::List.new(file: @file).value)
|
132
|
+
end
|
133
|
+
|
134
|
+
def app_path
|
135
|
+
@app_path ||= case device_type
|
136
|
+
when Device::MACOS
|
137
|
+
File.dirname(@file)
|
138
|
+
else
|
139
|
+
File.expand_path('../', @file)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
require 'forwardable'
|
4
4
|
|
5
5
|
module AppInfo
|
6
|
-
# iOS
|
6
|
+
# iOS Framework parser
|
7
7
|
class Framework
|
8
8
|
extend Forwardable
|
9
9
|
|
@@ -11,7 +11,7 @@ module AppInfo
|
|
11
11
|
files = Dir.glob(File.join(path, name.to_s, '*'))
|
12
12
|
return [] if files.empty?
|
13
13
|
|
14
|
-
files.each_with_object([]) do |file, obj|
|
14
|
+
files.sort.each_with_object([]) do |file, obj|
|
15
15
|
obj << new(file)
|
16
16
|
end
|
17
17
|
end
|
@@ -41,7 +41,7 @@ module AppInfo
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def info
|
44
|
-
@info ||= InfoPlist.new(file)
|
44
|
+
@info ||= InfoPlist.new(File.join(file, 'Info.plist'))
|
45
45
|
end
|
46
46
|
|
47
47
|
def to_s
|