app-info 2.5.4 → 2.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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 #:nodoc:
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(humanable = false)
134
- return Util.size_to_humanable(@size) if humanable
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
- humanable_size: size(true)
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 Plugin parser
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
data/lib/app_info/ipa.rb CHANGED
@@ -4,7 +4,6 @@ require 'macho'
4
4
  require 'fileutils'
5
5
  require 'forwardable'
6
6
  require 'cfpropertylist'
7
- require 'app_info/util'
8
7
 
9
8
  module AppInfo
10
9
  # IPA parser
@@ -28,8 +27,8 @@ module AppInfo
28
27
  @file = file
29
28
  end
30
29
 
31
- def size(humanable = false)
32
- AppInfo::Util.file_size(@file, humanable)
30
+ def size(human_size: false)
31
+ AppInfo::Util.file_size(@file, human_size)
33
32
  end
34
33
 
35
34
  def os
@@ -39,7 +38,7 @@ module AppInfo
39
38
 
40
39
  def_delegators :info, :iphone?, :ipad?, :universal?, :build_version, :name,
41
40
  :release_version, :identifier, :bundle_id, :display_name,
42
- :bundle_name, :icons, :min_sdk_version, :device_type
41
+ :bundle_name, :min_sdk_version, :min_os_version, :device_type
43
42
 
44
43
  def_delegators :mobileprovision, :devices, :team_name, :team_identifier,
45
44
  :profile_name, :expired_date
@@ -83,8 +82,14 @@ module AppInfo
83
82
  end
84
83
  alias architectures archs
85
84
 
85
+ def icons(uncrush: true)
86
+ @icons ||= icons_path.each_with_object([]) do |file, obj|
87
+ obj << build_icon_metadata(file, uncrush: uncrush)
88
+ end
89
+ end
90
+
86
91
  def stored?
87
- metadata? ? true : false
92
+ !!metadata?
88
93
  end
89
94
 
90
95
  def plugins
@@ -107,7 +112,7 @@ module AppInfo
107
112
  end
108
113
 
109
114
  def mobileprovision?
110
- File.exist?mobileprovision_path
115
+ File.exist?(mobileprovision_path)
111
116
  end
112
117
 
113
118
  def mobileprovision_path
@@ -139,24 +144,54 @@ module AppInfo
139
144
  end
140
145
 
141
146
  def info
142
- @info ||= InfoPlist.new(app_path)
147
+ @info ||= InfoPlist.new(info_path)
148
+ end
149
+
150
+ def info_path
151
+ @info_path ||= File.join(app_path, 'Info.plist')
143
152
  end
144
153
 
145
154
  def app_path
146
155
  @app_path ||= Dir.glob(File.join(contents, 'Payload', '*.app')).first
147
156
  end
148
157
 
158
+ IPHONE_KEY = 'CFBundleIcons'
159
+ IPAD_KEY = 'CFBundleIcons~ipad'
160
+
161
+ def icons_path
162
+ return @icons_path if @icons_path
163
+
164
+ @icons_path = []
165
+ icon_keys.each do |name|
166
+ filenames = info.try(:[], name)
167
+ .try(:[], 'CFBundlePrimaryIcon')
168
+ .try(:[], 'CFBundleIconFiles')
169
+
170
+ next if filenames.nil? || filenames.empty?
171
+
172
+ filenames.each do |filename|
173
+ Dir.glob(File.join(app_path, "#{filename}*")).find_all.each do |file|
174
+ @icons_path << file
175
+ end
176
+ end
177
+ end
178
+
179
+ @icons_path
180
+ end
181
+
149
182
  def clear!
150
183
  return unless @contents
151
184
 
152
185
  FileUtils.rm_rf(@contents)
153
186
 
154
187
  @contents = nil
155
- @icons = nil
156
188
  @app_path = nil
157
- @metadata = nil
158
- @metadata_path = nil
189
+ @info_path = nil
159
190
  @info = nil
191
+ @metadata_path = nil
192
+ @metadata = nil
193
+ @icons_path = nil
194
+ @icons = nil
160
195
  end
161
196
 
162
197
  def contents
@@ -165,18 +200,33 @@ module AppInfo
165
200
 
166
201
  private
167
202
 
168
- def icons_root_path
169
- iphone = 'CFBundleIcons'
170
- ipad = 'CFBundleIcons~ipad'
171
-
172
- case device_type
173
- when 'iPhone'
174
- [iphone]
175
- when 'iPad'
176
- [ipad]
177
- when 'Universal'
178
- [iphone, ipad]
179
- end
203
+ def build_icon_metadata(file, uncrush: true)
204
+ uncrushed_file = uncrush ? uncrush_png(file) : nil
205
+
206
+ {
207
+ name: File.basename(file),
208
+ file: file,
209
+ uncrushed_file: uncrushed_file,
210
+ dimensions: PngUncrush.dimensions(file)
211
+ }
212
+ end
213
+
214
+ # Uncrush png to normal png file (iOS)
215
+ def uncrush_png(src_file)
216
+ dest_file = Util.tempdir(src_file, prefix: 'uncrushed')
217
+ PngUncrush.decompress(src_file, dest_file)
218
+ File.exist?(dest_file) ? dest_file : nil
219
+ end
220
+
221
+ def icon_keys
222
+ @icon_keys ||= case device_type
223
+ when 'iPhone'
224
+ [IPHONE_KEY]
225
+ when 'iPad'
226
+ [IPAD_KEY]
227
+ when 'Universal'
228
+ [IPHONE_KEY, IPAD_KEY]
229
+ end
180
230
  end
181
231
  end
182
232
  end