cocoapods-core 0.17.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +20 -0
  3. data/README.md +36 -0
  4. data/lib/cocoapods-core/core_ui.rb +19 -0
  5. data/lib/cocoapods-core/dependency.rb +295 -0
  6. data/lib/cocoapods-core/gem_version.rb +6 -0
  7. data/lib/cocoapods-core/lockfile.rb +440 -0
  8. data/lib/cocoapods-core/platform.rb +171 -0
  9. data/lib/cocoapods-core/podfile/dsl.rb +459 -0
  10. data/lib/cocoapods-core/podfile/target_definition.rb +503 -0
  11. data/lib/cocoapods-core/podfile.rb +345 -0
  12. data/lib/cocoapods-core/requirement.rb +15 -0
  13. data/lib/cocoapods-core/source/validator.rb +183 -0
  14. data/lib/cocoapods-core/source.rb +284 -0
  15. data/lib/cocoapods-core/specification/consumer.rb +356 -0
  16. data/lib/cocoapods-core/specification/dsl/attribute.rb +245 -0
  17. data/lib/cocoapods-core/specification/dsl/attribute_support.rb +76 -0
  18. data/lib/cocoapods-core/specification/dsl/deprecations.rb +47 -0
  19. data/lib/cocoapods-core/specification/dsl/platform_proxy.rb +67 -0
  20. data/lib/cocoapods-core/specification/dsl.rb +1110 -0
  21. data/lib/cocoapods-core/specification/linter.rb +436 -0
  22. data/lib/cocoapods-core/specification/root_attribute_accessors.rb +152 -0
  23. data/lib/cocoapods-core/specification/set/presenter.rb +229 -0
  24. data/lib/cocoapods-core/specification/set/statistics.rb +277 -0
  25. data/lib/cocoapods-core/specification/set.rb +171 -0
  26. data/lib/cocoapods-core/specification/yaml.rb +60 -0
  27. data/lib/cocoapods-core/specification.rb +592 -0
  28. data/lib/cocoapods-core/standard_error.rb +84 -0
  29. data/lib/cocoapods-core/vendor/dependency.rb +264 -0
  30. data/lib/cocoapods-core/vendor/requirement.rb +208 -0
  31. data/lib/cocoapods-core/vendor/version.rb +333 -0
  32. data/lib/cocoapods-core/vendor.rb +56 -0
  33. data/lib/cocoapods-core/version.rb +99 -0
  34. data/lib/cocoapods-core/yaml_converter.rb +202 -0
  35. data/lib/cocoapods-core.rb +23 -0
  36. metadata +154 -0
@@ -0,0 +1,229 @@
1
+ require 'active_support/core_ext/array/conversions'
2
+
3
+ module Pod
4
+ class Specification
5
+ class Set
6
+
7
+ # Provides support for presenting a Pod described by a {Set} in a
8
+ # consistent way across clients of CocoaPods-Core.
9
+ #
10
+ class Presenter
11
+
12
+ # @return [Set] the set that should be presented.
13
+ #
14
+ attr_accessor :set
15
+
16
+ # @param [Set] set @see #set.
17
+ #
18
+ def initialize(set)
19
+ @set = set
20
+ end
21
+
22
+ #-----------------------------------------------------------------------#
23
+
24
+ # @!group Set Information
25
+
26
+ # @return [String] the name of the Pod.
27
+ #
28
+ def name
29
+ @set.name
30
+ end
31
+
32
+ # @return [Version] the highest version of available for the Pod.
33
+ #
34
+ def version
35
+ @set.versions.first
36
+ end
37
+
38
+ # @return [Array<Version>] all the versions available sorted
39
+ # ascendingly.
40
+ #
41
+ def versions
42
+ @set.versions.sort.reverse
43
+ end
44
+
45
+ # @return [String] all the versions available sorted from the highest
46
+ # to the lowest.
47
+ #
48
+ # @example Return example
49
+ #
50
+ # "1.5pre, 1.4 [master repo] - 1.4 [test_repo repo]"
51
+ #
52
+ # @note This method orders the sources by name.
53
+ #
54
+ def verions_by_source
55
+ result = []
56
+ versions_by_source = @set.versions_by_source
57
+ @set.sources.sort.each do |source|
58
+ versions = versions_by_source[source]
59
+ result << "#{versions.map(&:to_s) * ', '} [#{source.name} repo]"
60
+ end
61
+ result * ' - '
62
+ end
63
+
64
+ # @return [Array<String>] The name of the sources that contain the Pod
65
+ # sorted alphabetically.
66
+ #
67
+ def sources
68
+ @set.sources.map(&:name).sort
69
+ end
70
+
71
+ #-----------------------------------------------------------------------#
72
+
73
+ # @!group Specification Information
74
+
75
+ # @return [Specification] the specification of the {Set}. If no
76
+ # versions requirements where passed to the set it returns the
77
+ # highest available version.
78
+ #
79
+ def spec
80
+ @set.specification
81
+ end
82
+
83
+ # @return [String] the list of the authors of the Pod in sentence
84
+ # format.
85
+ #
86
+ # @example Output example
87
+ #
88
+ # "Author 1, Author 2 and Author 3"
89
+ #
90
+ # @note In ruby 1.8.7 the authors are sorted by name because the
91
+ # hash doesn't preserve the order in which they are defined
92
+ # in the podspec.
93
+ #
94
+ def authors
95
+ return '' unless spec.authors
96
+ if RUBY_VERSION == '1.8.7'
97
+ spec.authors.keys.sort.to_sentence
98
+ else
99
+ spec.authors.keys.to_sentence
100
+ end
101
+ end
102
+
103
+ # @return [String] the homepage of the pod.
104
+ #
105
+ def homepage
106
+ spec.homepage
107
+ end
108
+
109
+ # @return [String] a short description, expected to be 140 characters
110
+ # long of the Pod.
111
+ #
112
+ def summary
113
+ spec.summary
114
+ end
115
+
116
+ # @return [String] the description of the Pod, if no description is
117
+ # available the summary is returned.
118
+ #
119
+ def description
120
+ spec.description || spec.summary
121
+ end
122
+
123
+ # @return [String] the URL of the source of the Pod.
124
+ #
125
+ def source_url
126
+ url_keys = [:git, :svn, :http, :hg, :local ]
127
+ key = spec.source.keys.find { |k| url_keys.include?(k) }
128
+ key ? spec.source[key] : 'No source url'
129
+ end
130
+
131
+ # @return [String] the platforms supported by the Pod.
132
+ #
133
+ # @example
134
+ #
135
+ # "iOS"
136
+ # "iOS - OS X"
137
+ #
138
+ def platform
139
+ spec.available_platforms.sort { |a,b| a.to_s.downcase <=> b.to_s.downcase }.join(' - ')
140
+ end
141
+
142
+ # @return [String] the type of the license of the Pod.
143
+ #
144
+ # @example
145
+ #
146
+ # "MIT"
147
+ #
148
+ def license
149
+ spec.license[:type] if spec.license
150
+ end
151
+
152
+ # @return [Array] an array containing all the subspecs of the Pod.
153
+ #
154
+ def subspecs
155
+ (spec.recursive_subspecs.any? && spec.recursive_subspecs) || nil
156
+ end
157
+
158
+ #-----------------------------------------------------------------------#
159
+
160
+ # @!group Statistics
161
+
162
+ # @return [Time] the creation date of the first known `podspec` of the
163
+ # Pod.
164
+ #
165
+ def creation_date
166
+ Statistics.instance.creation_date(@set)
167
+ end
168
+
169
+ # @return [Integer] the GitHub likes of the repo of the Pod.
170
+ #
171
+ def github_watchers
172
+ Statistics.instance.github_watchers(@set)
173
+ end
174
+
175
+ # @return [Integer] the GitHub forks of the repo of the Pod.
176
+ #
177
+ def github_forks
178
+ Statistics.instance.github_forks(@set)
179
+ end
180
+
181
+ # @return [String] the relative time of the last push of the repo the Pod.
182
+ #
183
+ def github_last_activity
184
+ distance_from_now_in_words(Statistics.instance.github_pushed_at(@set))
185
+ end
186
+
187
+ #-----------------------------------------------------------------------#
188
+
189
+ private
190
+
191
+ # Computes a human readable string that represents a past date in
192
+ # relative terms.
193
+ #
194
+ # @param [Time, String] from_time
195
+ # the date that should be represented.
196
+ #
197
+ # @example Possible outputs
198
+ #
199
+ # "less than a week ago"
200
+ # "15 days ago"
201
+ # "3 month ago"
202
+ # "more than a year ago"
203
+ #
204
+ # @return [String] a string that represents a past date.
205
+ #
206
+ def distance_from_now_in_words(from_time)
207
+ return nil unless from_time
208
+ from_time = Time.parse(from_time) unless from_time.is_a?(Time)
209
+ to_time = Time.now
210
+ distance_in_days = (((to_time - from_time).abs)/60/60/24).round
211
+
212
+ case distance_in_days
213
+ when 0..7
214
+ "less than a week ago"
215
+ when 8..29
216
+ "#{distance_in_days} days ago"
217
+ when 30..45
218
+ "1 month ago"
219
+ when 46..365
220
+ "#{(distance_in_days.to_f / 30).round} months ago"
221
+ else
222
+ "more than a year ago"
223
+ end
224
+ end
225
+ end
226
+ end
227
+ end
228
+ end
229
+
@@ -0,0 +1,277 @@
1
+ module Pod
2
+ class Specification
3
+ class Set
4
+
5
+ # The statistics class provides information about one or more {Set} that
6
+ # is not readily available because expensive to compute or provided by a
7
+ # remote source.
8
+ #
9
+ # The class provides also facilities to work with a collection of sets.
10
+ # It always caches in memory the computed values and it can take an
11
+ # optional path to cache file that it is responsible of populating and
12
+ # invalidating.
13
+ #
14
+ # To reuse the in memory cache and to minimize the disk access to the
15
+ # cache file a shared instance is also available.
16
+ #
17
+ class Statistics
18
+
19
+ # @return [Statistics] the shared statistics instance.
20
+ #
21
+ def self.instance
22
+ @instance ||= new
23
+ end
24
+
25
+ # Allows to set the shared instance.
26
+ #
27
+ # @param [Statistics] instance
28
+ # the new shared instance or nil.
29
+ #
30
+ # @return [Statistics] the shared statistics instance.
31
+ #
32
+ def self.instance=(instance)
33
+ @instance = instance
34
+ end
35
+
36
+ # @return [Pathname] the path to the optional cache file.
37
+ #
38
+ # @note The cache file can be specified after initialization, but
39
+ # it has to be configured before requiring any value, otherwise
40
+ # it is ignored.
41
+ #
42
+ attr_accessor :cache_file
43
+
44
+ # @return [Integer] the number of seconds after which the caches of
45
+ # values that might changed are discarded.
46
+ #
47
+ # @note If not specified on initialization defaults to 3 days.
48
+ #
49
+ attr_accessor :cache_expiration
50
+
51
+ # @param [Pathname] cache_file @see cache_file
52
+ #
53
+ # @param [Integer] cache_expiration @see cache_expiration
54
+ #
55
+ def initialize(cache_file = nil, cache_expiration = (60 * 60 * 24 * 3))
56
+ require 'yaml'
57
+ # This is to make sure Faraday doesn't warn the user about the
58
+ # `system_timer` gem missing.
59
+ old_warn, $-w = $-w, nil
60
+ begin
61
+ require 'faraday'
62
+ ensure
63
+ $-w = old_warn
64
+ end
65
+ require 'octokit'
66
+
67
+ @cache_file = cache_file
68
+ @cache_expiration = cache_expiration
69
+ end
70
+
71
+ #---------------------------------------------------------------------#
72
+
73
+ # @!group Accessing the statistics
74
+
75
+ # Computes the date in which the first podspec of a set was committed
76
+ # on its git source.
77
+ #
78
+ # @param [Set] set
79
+ # the set for the Pod whose creation date is needed.
80
+ #
81
+ # @note The set should be generated with only the source that is
82
+ # analyzed. If there are more than one the first one is
83
+ # processed.
84
+ #
85
+ # @note This method needs to traverse the git history of the repo and
86
+ # thus incurs in a performance hit.
87
+ #
88
+ # @return [Time] the date in which a Pod appeared for the first time on
89
+ # the {Source}.
90
+ #
91
+ def creation_date(set)
92
+ date = compute_creation_date(set)
93
+ save_cache
94
+ date
95
+ end
96
+
97
+ # Computes the date in which the first podspec of each given set was
98
+ # committed on its git source.
99
+ #
100
+ # @param [Array<Set>] sets
101
+ # the list of the sets for the Pods whose creation date is
102
+ # needed.
103
+ #
104
+ # @note @see creation_date
105
+ #
106
+ # @note This method is optimized for multiple sets because it saves
107
+ # the cache file only once.
108
+ #
109
+ # @return [Array<Time>] the list of the dates in which the Pods
110
+ # appeared for the first time on the {Source}.
111
+ #
112
+ def creation_dates(sets)
113
+ dates = {}
114
+ sets.each { |set| dates[set.name] = compute_creation_date(set) }
115
+ save_cache
116
+ dates
117
+ end
118
+
119
+ # Computes the number of likes that a Pod has on Github.
120
+ #
121
+ # @param [Set] set
122
+ # the set of the Pod.
123
+ #
124
+ # @return [Integer] the number of likes or nil if the Pod is not hosted
125
+ # on GitHub.
126
+ #
127
+ def github_watchers(set)
128
+ github_stats_if_needed(set)
129
+ get_value(set, :gh_watchers)
130
+ end
131
+
132
+ # Computes the number of forks that a Pod has on Github.
133
+ #
134
+ # @param [Set] set @see github_watchers
135
+ #
136
+ # @return [Integer] the number of forks or nil if the Pod is not hosted
137
+ # on GitHub.
138
+ #
139
+ def github_forks(set)
140
+ github_stats_if_needed(set)
141
+ get_value(set, :gh_forks)
142
+ end
143
+
144
+ # Computes the number of likes that a Pod has on Github.
145
+ #
146
+ # @param [Set] set @see github_watchers
147
+ #
148
+ # @return [Time] the time of the last push or nil if the Pod is not
149
+ # hosted on GitHub.
150
+ #
151
+ def github_pushed_at(set)
152
+ github_stats_if_needed(set)
153
+ string_time = get_value(set, :pushed_at)
154
+ Time.parse(string_time) if string_time
155
+ end
156
+
157
+ #---------------------------------------------------------------------#
158
+
159
+ private
160
+
161
+ # @return [Hash{String => Hash}] the in-memory cache, where for each
162
+ # set is stored a hash with the result of the computations.
163
+ #
164
+ def cache
165
+ unless @cache
166
+ if cache_file && cache_file.exist?
167
+ @cache = YAML.load(cache_file.read)
168
+ else
169
+ @cache = {}
170
+ end
171
+ end
172
+ @cache
173
+ end
174
+
175
+ # Returns the value for the given key of a set stored in the cache, if
176
+ # available.
177
+ #
178
+ # @param [Set] set
179
+ # the set for which the value is needed.
180
+ #
181
+ # @param [Symbol] key
182
+ # the key of the value.
183
+ #
184
+ # @return [Object] the value or nil.
185
+ #
186
+ def get_value(set, key)
187
+ if cache[set.name] && cache[set.name][key]
188
+ cache[set.name][key]
189
+ end
190
+ end
191
+
192
+ # Stores the given value of a set for the given key in the cache.
193
+ #
194
+ # @param [Set] set
195
+ # the set for which the value has to be stored.
196
+ #
197
+ # @param [Symbol] key
198
+ # the key of the value.
199
+ #
200
+ # @param [Object] value
201
+ # the value to store.
202
+ #
203
+ # @return [Object] the value or nil.
204
+ #
205
+ def set_value(set, key, value)
206
+ cache[set.name] ||= {}
207
+ cache[set.name][key] = value
208
+ end
209
+
210
+ # Saves the in-memory cache to the path of cache file if specified.
211
+ #
212
+ # @return [void]
213
+ #
214
+ def save_cache
215
+ if cache_file
216
+ yaml = YAML.dump(cache)
217
+ File.open(cache_file, 'w') { |f| f.write(yaml) }
218
+ end
219
+ end
220
+
221
+ # Analyzes the history of the git repository of the {Source} of the
222
+ # given {Set} to find when its folder was created.
223
+ #
224
+ # @param [Set] set
225
+ # the set for which the creation date is needed.
226
+ #
227
+ # @return [Time] the date in which a Pod was created.
228
+ #
229
+ def compute_creation_date(set)
230
+ date = get_value(set, :creation_date)
231
+ unless date
232
+ Dir.chdir(set.sources.first.repo) do
233
+ git_log = `git log --first-parent --format=%ct #{set.name}`
234
+ creation_date = git_log.split("\n").last.to_i
235
+ date = Time.at(creation_date)
236
+ end
237
+ set_value(set, :creation_date, date)
238
+ end
239
+ date
240
+ end
241
+
242
+ # Retrieved the GitHub information from the API for the given set and
243
+ # stores it in the in-memory cache.
244
+ #
245
+ # @note If there is a valid cache and it was generated withing the
246
+ # expiration time frame this method does nothing.
247
+ #
248
+ # @param [Set] set
249
+ # the set for which the GitHub information is needed.
250
+ #
251
+ # @return [void]
252
+ #
253
+ def github_stats_if_needed(set)
254
+ update_date = get_value(set, :gh_date)
255
+ return if update_date && update_date > (Time.now - cache_expiration)
256
+
257
+ spec = set.specification
258
+ url = spec.source[:git] || ''
259
+ repo_id = url[/github.com\/([^\/\.]*\/[^\/\.]*)\.*/, 1]
260
+ return unless repo_id
261
+
262
+ begin
263
+ repo = Octokit.repo(repo_id)
264
+ rescue
265
+ return
266
+ end
267
+
268
+ set_value(set, :gh_watchers, repo['watchers'])
269
+ set_value(set, :gh_forks, repo['forks'])
270
+ set_value(set, :pushed_at, repo['pushed_at'])
271
+ set_value(set, :gh_date, Time.now)
272
+ save_cache
273
+ end
274
+ end
275
+ end
276
+ end
277
+ end
@@ -0,0 +1,171 @@
1
+ require 'active_support/core_ext/array/conversions'
2
+
3
+ require 'cocoapods-core/specification/set/presenter'
4
+ require 'cocoapods-core/specification/set/statistics'
5
+
6
+
7
+ module Pod
8
+ class Specification
9
+
10
+ # A Specification::Set is responsible of handling all the specifications of
11
+ # a Pod. This class stores the information of the dependencies that required
12
+ # a Pod in the resolution process.
13
+ #
14
+ # @note The alphabetical order of the sets is used to select a
15
+ # specification if multiple are available for a given version.
16
+ #
17
+ # @note The set class is not and should be not aware of the backing store
18
+ # of a Source.
19
+ #
20
+ class Set
21
+
22
+ # @return [String] the name of the Pod.
23
+ #
24
+ attr_reader :name
25
+
26
+ # @return [Array<Source>] the sources that contain the specifications for
27
+ # the available versions of a Pod.
28
+ #
29
+ attr_reader :sources
30
+
31
+ # @param [String] name
32
+ # the name of the Pod.
33
+ #
34
+ # @param [Array<Source>,Source] sources
35
+ # the sources that contain a Pod.
36
+ #
37
+ def initialize(name, sources = [])
38
+ @name = name
39
+ sources = sources.is_a?(Array) ? sources : [sources]
40
+ @sources = sources.sort_by(&:name)
41
+ @required_by = []
42
+ @dependencies = []
43
+ end
44
+
45
+ # Stores a dependency on the Pod.
46
+ #
47
+ # @param [Dependency] dependency
48
+ # a dependency that requires the Pod.
49
+ #
50
+ # @param [String] dependent_name
51
+ # the name of the owner of the dependency. It is used only to
52
+ # display the Pod::Informative.
53
+ #
54
+ # @raise If the versions requirement of the dependency are not
55
+ # compatible with the previously stored dependencies.
56
+ #
57
+ # @todo This should simply return a boolean. Is cocoaPods that should raise.
58
+ #
59
+ # @return [void]
60
+ #
61
+ def required_by(dependency, dependent_name)
62
+ unless @required_by.empty? || dependency.requirement.satisfied_by?(Version.new(required_version.to_s))
63
+ raise StandardError, "#{dependent_name} tries to activate `#{dependency}', but already activated version `#{required_version}' by #{@required_by.to_sentence}."
64
+ end
65
+ @specification = nil
66
+ @required_by << dependent_name
67
+ @dependencies << dependency
68
+ end
69
+
70
+ # @return [Dependency] a dependency that includes all the versions
71
+ # requirements of the stored dependencies.
72
+ #
73
+ def dependency
74
+ @dependencies.inject(Dependency.new(name)) do |previous, dependency|
75
+ previous.merge(dependency.to_root_dependency)
76
+ end
77
+ end
78
+
79
+ # @return [Specification] the top level specification of the Pod for the
80
+ # {#required_version}.
81
+ #
82
+ # @note If multiple sources have a specification for the
83
+ # {#required_version} The alphabetical order of their names is
84
+ # used to disambiguate.
85
+ #
86
+ def specification
87
+ unless @specification
88
+ sources = []
89
+ versions_by_source.each{ |source, versions| sources << source if versions.include?(required_version) }
90
+ source = sources.sort_by(&:name).first
91
+ @specification = source.specification(name, required_version)
92
+ end
93
+ @specification
94
+ end
95
+
96
+ # @return [Version] the highest version that satisfies the stored
97
+ # dependencies.
98
+ #
99
+ # @todo This should simply return nil. CocoaPods should raise instead.
100
+ #
101
+ def required_version
102
+ versions.find { |v| dependency.match?(name, v) } ||
103
+ (raise StandardError, "Required version (#{dependency}) not found for `#{name}'.\nAvailable versions: #{versions.join(', ')}")
104
+ end
105
+
106
+ # @return [Array<Version>] all the available versions for the Pod, sorted
107
+ # from highest to lowest.
108
+ #
109
+ def versions
110
+ versions_by_source.values.flatten.uniq.sort.reverse
111
+ end
112
+
113
+ # @return [Hash{Source => Version}] all the available versions for the
114
+ # Pod grouped by source.
115
+ #
116
+ def versions_by_source
117
+ result = {}
118
+ sources.each do |source|
119
+ result[source] = source.versions(name)
120
+ end
121
+ result
122
+ end
123
+
124
+ def ==(other)
125
+ self.class === other && @name == other.name && @sources.map(&:name) == other.sources.map(&:name)
126
+ end
127
+
128
+ def to_s
129
+ "#<#{self.class.name} for `#{name}' with required version `#{required_version}' available at `#{sources.map(&:name) * ', '}'>"
130
+ end
131
+ alias_method :inspect, :to_s
132
+
133
+ #-------------------------------------------------------------------------#
134
+
135
+ # The Set::External class handles Pods from external sources. Pods from
136
+ # external sources don't use the {Source} and are initialized by a given
137
+ # specification.
138
+ #
139
+ # @note External sources *don't* support subspecs.
140
+ #
141
+ class External < Set
142
+
143
+ attr_reader :specification
144
+
145
+ def initialize(spec)
146
+ @specification = spec.root
147
+ super(@specification.name)
148
+ end
149
+
150
+ def ==(other)
151
+ self.class === other && @specification == other.specification
152
+ end
153
+
154
+ def required_by(dependency, dependent_name)
155
+ before = @specification
156
+ super(dependency, dependent_name)
157
+ ensure
158
+ @specification = before
159
+ end
160
+
161
+ def specification_path
162
+ raise StandardError, "specification_path"
163
+ end
164
+
165
+ def versions
166
+ [@specification.version]
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end