cocoapods-core 0.17.0.rc1

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.
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