cocoapods 0.14.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.md +42 -9
- data/bin/pod +4 -0
- data/lib/cocoapods.rb +2 -2
- data/lib/cocoapods/command.rb +2 -25
- data/lib/cocoapods/command/install.rb +1 -2
- data/lib/cocoapods/command/linter.rb +24 -4
- data/lib/cocoapods/command/list.rb +16 -11
- data/lib/cocoapods/command/outdated.rb +2 -4
- data/lib/cocoapods/command/push.rb +10 -10
- data/lib/cocoapods/command/repo.rb +22 -20
- data/lib/cocoapods/command/search.rb +6 -4
- data/lib/cocoapods/command/setup.rb +15 -14
- data/lib/cocoapods/command/spec.rb +17 -14
- data/lib/cocoapods/command/update.rb +9 -1
- data/lib/cocoapods/config.rb +12 -4
- data/lib/cocoapods/dependency.rb +18 -15
- data/lib/cocoapods/downloader/git.rb +41 -28
- data/lib/cocoapods/downloader/http.rb +10 -3
- data/lib/cocoapods/downloader/mercurial.rb +6 -4
- data/lib/cocoapods/downloader/subversion.rb +6 -2
- data/lib/cocoapods/executable.rb +6 -6
- data/lib/cocoapods/generator/acknowledgements/markdown.rb +1 -0
- data/lib/cocoapods/installer.rb +73 -57
- data/lib/cocoapods/installer/target_installer.rb +15 -11
- data/lib/cocoapods/installer/user_project_integrator.rb +25 -16
- data/lib/cocoapods/local_pod.rb +34 -12
- data/lib/cocoapods/podfile.rb +15 -0
- data/lib/cocoapods/resolver.rb +33 -39
- data/lib/cocoapods/source.rb +172 -48
- data/lib/cocoapods/specification.rb +48 -32
- data/lib/cocoapods/specification/set.rb +102 -34
- data/lib/cocoapods/specification/statistics.rb +1 -1
- data/lib/cocoapods/user_interface.rb +215 -0
- data/lib/cocoapods/user_interface/ui_pod.rb +130 -0
- metadata +7 -7
- data/lib/cocoapods/command/presenter.rb +0 -61
- data/lib/cocoapods/command/presenter/cocoa_pod.rb +0 -118
@@ -2,77 +2,145 @@ require 'active_support/core_ext/array/conversions'
|
|
2
2
|
|
3
3
|
module Pod
|
4
4
|
class Specification
|
5
|
+
|
6
|
+
# A Specification::Set is resposible of handling all the specifications of
|
7
|
+
# a Pod. This class stores the information of the dependencies that reuired
|
8
|
+
# a Pod in the resolution process.
|
9
|
+
#
|
10
|
+
# @note The alpahbetical order of the sets is used to select a specification
|
11
|
+
# if multiple are available for a given version.
|
12
|
+
#
|
13
|
+
# @note The set class is not and should be not aware of the backing store
|
14
|
+
# of a Source.
|
15
|
+
#
|
5
16
|
class Set
|
6
|
-
attr_reader :pod_dir
|
7
17
|
|
8
|
-
|
9
|
-
|
10
|
-
|
18
|
+
# @return [String] The name of the Pod.
|
19
|
+
#
|
20
|
+
attr_reader :name
|
21
|
+
|
22
|
+
# @return [Array<Source>] The sources that contain the specifications for
|
23
|
+
# the available versions of a Pod.
|
24
|
+
#
|
25
|
+
attr_reader :sources
|
26
|
+
|
27
|
+
# @param [String] name The name of the Pod.
|
28
|
+
#
|
29
|
+
# @param [Array<Source>,Source] sources
|
30
|
+
# The sources that contain a Pod.
|
31
|
+
#
|
32
|
+
def initialize(name, sources)
|
33
|
+
@name = name
|
34
|
+
sources = sources.is_a?(Array) ? sources : [sources]
|
35
|
+
@sources = sources.sort_by(&:name)
|
36
|
+
@required_by = []
|
11
37
|
@dependencies = []
|
12
38
|
end
|
13
39
|
|
40
|
+
# @return [void] Stores a dependency on the Pod.
|
41
|
+
#
|
42
|
+
# @param [Dependency] dependency A dependency that requires the Pod.
|
43
|
+
#
|
44
|
+
# @param [String] dependent_name The name of the owner of the
|
45
|
+
# dependency. It is used only to display
|
46
|
+
# the Pod::Informative.
|
47
|
+
#
|
48
|
+
# @raises If the versions requirement of the dependency are not
|
49
|
+
# compatible with the previously stored dependencies.
|
50
|
+
#
|
14
51
|
def required_by(dependency, dependent_name)
|
15
52
|
unless @required_by.empty? || dependency.requirement.satisfied_by?(Gem::Version.new(required_version.to_s))
|
16
|
-
raise Informative, "#{dependent_name} tries to activate `#{dependency}', "
|
17
|
-
"but already activated version `#{required_version}' " \
|
18
|
-
"by #{@required_by.to_sentence}."
|
53
|
+
raise Informative, "#{dependent_name} tries to activate `#{dependency}', but already activated version `#{required_version}' by #{@required_by.to_sentence}."
|
19
54
|
end
|
20
55
|
@specification = nil
|
21
|
-
@required_by
|
56
|
+
@required_by << dependent_name
|
22
57
|
@dependencies << dependency
|
23
58
|
end
|
24
59
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
60
|
+
# @return [Dependency] A dependency including all the versions
|
61
|
+
# requirements of the stored dependencies.
|
62
|
+
#
|
29
63
|
def dependency
|
30
64
|
@dependencies.inject(Dependency.new(name)) do |previous, dependency|
|
31
65
|
previous.merge(dependency.to_top_level_spec_dependency)
|
32
66
|
end
|
33
67
|
end
|
34
68
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
69
|
+
# @return [Specification] The specification for the given subspec name,
|
70
|
+
# from {specification}.
|
71
|
+
#
|
72
|
+
# @param [String] name The name of the specification. It can be the
|
73
|
+
# name of the top level parent or the name of a
|
74
|
+
# subspec.
|
75
|
+
#
|
76
|
+
# @see specification
|
77
|
+
#
|
78
|
+
def specification_by_name(name)
|
79
|
+
specification.top_level_parent.subspec_by_name(name)
|
41
80
|
end
|
42
81
|
|
82
|
+
# @return [Specification] The top level specification of the Pod for the
|
83
|
+
# {required_version}.
|
84
|
+
#
|
85
|
+
# @note If multiple sources have a specification for the
|
86
|
+
# {required_version} The alpahbetical order of their names is used
|
87
|
+
# to disambiguate.
|
88
|
+
#
|
43
89
|
def specification
|
44
|
-
@specification
|
90
|
+
unless @specification
|
91
|
+
sources = []
|
92
|
+
versions_by_source.each{ |source, versions| sources << source if versions.include?(required_version) }
|
93
|
+
source = sources.sort_by(&:name).first
|
94
|
+
@specification = source.specification(name, required_version)
|
95
|
+
end
|
96
|
+
@specification
|
45
97
|
end
|
46
98
|
|
47
|
-
#
|
99
|
+
# @return [Version] The highest version that satisfies {dependency}.
|
100
|
+
#
|
48
101
|
def required_version
|
49
102
|
versions.find { |v| dependency.match?(name, v) } ||
|
50
103
|
raise(Informative, "Required version (#{dependency}) not found for `#{name}'.\nAvailable versions: #{versions.join(', ')}")
|
51
104
|
end
|
52
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 groupped 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
|
+
|
53
124
|
def ==(other)
|
54
|
-
self.class === other && @
|
125
|
+
self.class === other && @name == other.name && @sources.map(&:name) == other.sources.map(&:name)
|
55
126
|
end
|
56
127
|
|
57
128
|
def to_s
|
58
|
-
"#<#{self.class.name} for `#{name}' with required version `#{required_version}' at `#{
|
129
|
+
"#<#{self.class.name} for `#{name}' with required version `#{required_version}' available at `#{sources.map(&:name) * ', '}'>"
|
59
130
|
end
|
60
131
|
alias_method :inspect, :to_s
|
61
132
|
|
62
|
-
#
|
63
|
-
#
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
end.compact.sort.reverse
|
69
|
-
end
|
70
|
-
|
133
|
+
# The Set::External class handles Pods from external sources. Pods from
|
134
|
+
# external sources don't use the {Pod::Sources} and are intialized by a
|
135
|
+
# given specification.
|
136
|
+
#
|
137
|
+
# @note External sources *don't* support subspecs.
|
138
|
+
#
|
71
139
|
class External < Set
|
72
140
|
def initialize(specification)
|
73
141
|
@specification = specification
|
74
|
-
@required_by
|
75
|
-
@dependencies
|
142
|
+
@required_by = []
|
143
|
+
@dependencies = []
|
76
144
|
end
|
77
145
|
|
78
146
|
def name
|
@@ -80,7 +148,7 @@ module Pod
|
|
80
148
|
end
|
81
149
|
|
82
150
|
def ==(other)
|
83
|
-
self.class === other &&
|
151
|
+
self.class === other && @specification == other.specification
|
84
152
|
end
|
85
153
|
|
86
154
|
def required_by(dependency, dependent_name)
|
@@ -78,7 +78,7 @@ module Pod
|
|
78
78
|
def compute_creation_date(set, save = true)
|
79
79
|
date = get_value(set, :creation_date)
|
80
80
|
unless date
|
81
|
-
Dir.chdir(set.
|
81
|
+
Dir.chdir(set.sources.first.repo) do
|
82
82
|
date = Time.at(`git log --first-parent --format=%ct #{set.name}`.split("\n").last.to_i)
|
83
83
|
end
|
84
84
|
set_value(set, :creation_date, date)
|
@@ -0,0 +1,215 @@
|
|
1
|
+
module Pod
|
2
|
+
require 'colored'
|
3
|
+
module UserInterface
|
4
|
+
|
5
|
+
autoload :UIPod, 'cocoapods/user_interface/ui_pod'
|
6
|
+
|
7
|
+
@title_colors = %w|yellow green|
|
8
|
+
@title_level = 0
|
9
|
+
@indentation_level = 2
|
10
|
+
@treat_titles_as_messages = false
|
11
|
+
|
12
|
+
class << self
|
13
|
+
include Config::Mixin
|
14
|
+
|
15
|
+
attr_accessor :indentation_level, :title_level
|
16
|
+
|
17
|
+
# Prints a title taking an optional verbose prefix and
|
18
|
+
# a relative indentation valid for the UI action in the passed
|
19
|
+
# block.
|
20
|
+
#
|
21
|
+
# In verbose mode titles are printed with a color according
|
22
|
+
# to their level. In normal mode titles are printed only if
|
23
|
+
# they have nesting level smaller than 2.
|
24
|
+
#
|
25
|
+
# TODO: refactor to title (for always visible titles like search)
|
26
|
+
# and sections (titles that reppresent collapsible sections).
|
27
|
+
#
|
28
|
+
def section(title, verbose_prefix = '', relative_indentation = 0)
|
29
|
+
if config.verbose?
|
30
|
+
title(title, verbose_prefix, relative_indentation)
|
31
|
+
elsif title_level < 2
|
32
|
+
puts title
|
33
|
+
end
|
34
|
+
|
35
|
+
self.indentation_level += relative_indentation
|
36
|
+
self.title_level += 1
|
37
|
+
yield if block_given?
|
38
|
+
self.indentation_level -= relative_indentation
|
39
|
+
self.title_level -= 1
|
40
|
+
end
|
41
|
+
|
42
|
+
# A title oposed to a section is always visible
|
43
|
+
#
|
44
|
+
def title(title, verbose_prefix = '', relative_indentation = 2)
|
45
|
+
if(@treat_titles_as_messages)
|
46
|
+
message(title, verbose_prefix)
|
47
|
+
else
|
48
|
+
title = verbose_prefix + title if config.verbose?
|
49
|
+
title = "\n#{title}" if @title_level < 2
|
50
|
+
if (color = @title_colors[@title_level])
|
51
|
+
title = title.send(color)
|
52
|
+
end
|
53
|
+
puts "#{title}"
|
54
|
+
end
|
55
|
+
|
56
|
+
self.indentation_level += relative_indentation
|
57
|
+
self.title_level += 1
|
58
|
+
yield if block_given?
|
59
|
+
self.indentation_level -= relative_indentation
|
60
|
+
self.title_level -= 1
|
61
|
+
end
|
62
|
+
|
63
|
+
# def title(title, verbose_prefix = '', relative_indentation = 2)
|
64
|
+
# end
|
65
|
+
|
66
|
+
# Prints a verbose message taking an optional verbose prefix and
|
67
|
+
# a relative indentation valid for the UI action in the passed
|
68
|
+
# block.
|
69
|
+
#
|
70
|
+
# TODO: clean interface.
|
71
|
+
#
|
72
|
+
def message(message, verbose_prefix = '', relative_indentation = 2)
|
73
|
+
message = verbose_prefix + message if config.verbose?
|
74
|
+
puts_indented message if config.verbose?
|
75
|
+
|
76
|
+
self.indentation_level += relative_indentation
|
77
|
+
yield if block_given?
|
78
|
+
self.indentation_level -= relative_indentation
|
79
|
+
end
|
80
|
+
|
81
|
+
# Prints an info to the user. The info is always displayed.
|
82
|
+
# It respects the current indentation level only in verbose
|
83
|
+
# mode.
|
84
|
+
#
|
85
|
+
# Any title printed in the optional block is treated as a message.
|
86
|
+
#
|
87
|
+
def info(message)
|
88
|
+
indentation = config.verbose? ? self.indentation_level : 0
|
89
|
+
indented = wrap_string(message, " " * indentation)
|
90
|
+
puts(indented)
|
91
|
+
|
92
|
+
self.indentation_level += 2
|
93
|
+
@treat_titles_as_messages = true
|
94
|
+
yield if block_given?
|
95
|
+
@treat_titles_as_messages = false
|
96
|
+
self.indentation_level -= 2
|
97
|
+
end
|
98
|
+
|
99
|
+
# Prints an important message to the user.
|
100
|
+
#
|
101
|
+
# @param [String] message The message to print.
|
102
|
+
#
|
103
|
+
# return [void]
|
104
|
+
#
|
105
|
+
def notice(message)
|
106
|
+
puts("\n[!] #{message}".green)
|
107
|
+
end
|
108
|
+
|
109
|
+
# Prints an important warning to the user optionally followed by actions
|
110
|
+
# that the user should take.
|
111
|
+
#
|
112
|
+
# @param [String] message The message to print.
|
113
|
+
# @param [Actions] actions The actions that the user should take.
|
114
|
+
#
|
115
|
+
# return [void]
|
116
|
+
#
|
117
|
+
def warn(message, actions)
|
118
|
+
puts("\n[!] #{message}".yellow)
|
119
|
+
actions.each do |action|
|
120
|
+
indented = wrap_string(action, " - ")
|
121
|
+
puts(indented)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns a string containing relative location of a path from the Podfile.
|
126
|
+
# The returned path is quoted. If the argument is nit it returns the
|
127
|
+
# empty string.
|
128
|
+
#
|
129
|
+
def path(pathname)
|
130
|
+
if pathname
|
131
|
+
"`./#{pathname.relative_path_from(config.project_podfile.dirname || Pathname.pwd)}'"
|
132
|
+
else
|
133
|
+
''
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Prints the textual repprensentation of a given set.
|
138
|
+
#
|
139
|
+
def pod(set, mode = :normal)
|
140
|
+
if mode == :name
|
141
|
+
puts_indented set.name
|
142
|
+
else
|
143
|
+
pod = UIPod.new(set)
|
144
|
+
title("\n-> #{pod.name} (#{pod.version})".green, '', 1) do
|
145
|
+
puts_indented pod.summary
|
146
|
+
labeled('Homepage', pod.homepage)
|
147
|
+
labeled('Source', pod.source_url)
|
148
|
+
labeled('Versions', pod.verions_by_source)
|
149
|
+
if mode == :stats
|
150
|
+
labeled('Pushed', pod.github_last_activity)
|
151
|
+
labeled('Authors', pod.authors) if pod.authors =~ /,/
|
152
|
+
labeled('Author', pod.authors) if pod.authors !~ /,/
|
153
|
+
labeled('License', pod.license)
|
154
|
+
labeled('Platform', pod.platform)
|
155
|
+
labeled('Watchers', pod.github_watchers)
|
156
|
+
labeled('Forks', pod.github_forks)
|
157
|
+
end
|
158
|
+
labeled('Sub specs', pod.subspecs)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# Prints a message with a label.
|
164
|
+
#
|
165
|
+
def labeled(label, value)
|
166
|
+
if value
|
167
|
+
''.tap do |t|
|
168
|
+
t << " - #{label}:".ljust(16)
|
169
|
+
if value.is_a?(Array)
|
170
|
+
separator = "\n - "
|
171
|
+
puts_indented t << separator << value.join(separator)
|
172
|
+
else
|
173
|
+
puts_indented t << value.to_s << "\n"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# @!group Basic printing
|
180
|
+
|
181
|
+
# Prints a message unless config is silent.
|
182
|
+
#
|
183
|
+
def puts(message = '')
|
184
|
+
super(message) unless config.silent?
|
185
|
+
end
|
186
|
+
|
187
|
+
# Prints a message respecting the current indentation level and
|
188
|
+
# wrapping it to the terminal width if necessary.
|
189
|
+
#
|
190
|
+
def puts_indented(message = '')
|
191
|
+
indented = wrap_string(message, " " * self.indentation_level)
|
192
|
+
puts(indented)
|
193
|
+
end
|
194
|
+
|
195
|
+
private
|
196
|
+
|
197
|
+
# @!group Helpers
|
198
|
+
|
199
|
+
# Wraps a string taking into account the width of the terminal and an
|
200
|
+
# option indent. Adapted from http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
|
201
|
+
#
|
202
|
+
# @param [String] txt The string to wrap
|
203
|
+
#
|
204
|
+
# @param [String] indent The string to use to indent the result.
|
205
|
+
#
|
206
|
+
# @return [String] The formatted string.
|
207
|
+
#
|
208
|
+
def wrap_string(txt, indent = '')
|
209
|
+
width = `stty size`.split(' ')[1].to_i - indent.length
|
210
|
+
txt.strip.gsub(/(.{1,#{width}})( +|$)\n?|(.{#{width}})/, indent + "\\1\\3\n")
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
UI = UserInterface
|
215
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'active_support/core_ext/array/conversions'
|
2
|
+
|
3
|
+
module Pod
|
4
|
+
module UserInterface
|
5
|
+
class UIPod
|
6
|
+
attr_accessor :set
|
7
|
+
|
8
|
+
def initialize(set)
|
9
|
+
@set = set
|
10
|
+
end
|
11
|
+
|
12
|
+
# set information
|
13
|
+
def name
|
14
|
+
@set.name
|
15
|
+
end
|
16
|
+
|
17
|
+
def version
|
18
|
+
@set.versions.first
|
19
|
+
end
|
20
|
+
|
21
|
+
def versions
|
22
|
+
@set.versions.sort.reverse
|
23
|
+
end
|
24
|
+
|
25
|
+
def verions_by_source
|
26
|
+
result = []
|
27
|
+
@set.versions_by_source.each do |source, versions|
|
28
|
+
result << "#{versions.map(&:to_s) * ', '} [#{source.name} repo]"
|
29
|
+
end
|
30
|
+
result * ' - '
|
31
|
+
end
|
32
|
+
|
33
|
+
# @return [Array<String>]
|
34
|
+
#
|
35
|
+
def sources
|
36
|
+
@set.sources.map(&:name).sort
|
37
|
+
end
|
38
|
+
|
39
|
+
# specification information
|
40
|
+
def spec
|
41
|
+
@set.specification
|
42
|
+
end
|
43
|
+
|
44
|
+
def authors
|
45
|
+
spec.authors.keys.to_sentence
|
46
|
+
end
|
47
|
+
|
48
|
+
def homepage
|
49
|
+
spec.homepage
|
50
|
+
end
|
51
|
+
|
52
|
+
def description
|
53
|
+
spec.description
|
54
|
+
end
|
55
|
+
|
56
|
+
def summary
|
57
|
+
spec.summary
|
58
|
+
end
|
59
|
+
|
60
|
+
def source_url
|
61
|
+
spec.source.reject {|k,_| k == :commit || k == :tag }.values.first
|
62
|
+
end
|
63
|
+
|
64
|
+
def platform
|
65
|
+
spec.available_platforms.sort { |a,b| a.to_s.downcase <=> b.to_s.downcase }.join(' - ')
|
66
|
+
end
|
67
|
+
|
68
|
+
def license
|
69
|
+
spec.license[:type] if spec.license
|
70
|
+
end
|
71
|
+
|
72
|
+
# will return array of all subspecs (recursevly) or nil
|
73
|
+
def subspecs
|
74
|
+
(spec.recursive_subspecs.any? && spec.recursive_subspecs) || nil
|
75
|
+
end
|
76
|
+
|
77
|
+
# Statistics information
|
78
|
+
def creation_date
|
79
|
+
Pod::Specification::Statistics.instance.creation_date(@set)
|
80
|
+
end
|
81
|
+
|
82
|
+
def github_watchers
|
83
|
+
Pod::Specification::Statistics.instance.github_watchers(@set)
|
84
|
+
end
|
85
|
+
|
86
|
+
def github_forks
|
87
|
+
Pod::Specification::Statistics.instance.github_forks(@set)
|
88
|
+
end
|
89
|
+
|
90
|
+
def github_last_activity
|
91
|
+
distance_from_now_in_words(Pod::Specification::Statistics.instance.github_pushed_at(@set))
|
92
|
+
end
|
93
|
+
|
94
|
+
def ==(other)
|
95
|
+
self.class === other && @set == other.set
|
96
|
+
end
|
97
|
+
|
98
|
+
def eql?(other)
|
99
|
+
self.class === other && name.eql?(other.name)
|
100
|
+
end
|
101
|
+
|
102
|
+
def hash
|
103
|
+
name.hash
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def distance_from_now_in_words(from_time)
|
109
|
+
return nil unless from_time
|
110
|
+
from_time = Time.parse(from_time)
|
111
|
+
to_time = Time.now
|
112
|
+
distance_in_days = (((to_time - from_time).abs)/60/60/24).round
|
113
|
+
|
114
|
+
case distance_in_days
|
115
|
+
when 0..7
|
116
|
+
"less than a week ago"
|
117
|
+
when 8..29
|
118
|
+
"#{distance_in_days} days ago"
|
119
|
+
when 30..45
|
120
|
+
"1 month ago"
|
121
|
+
when 46..365
|
122
|
+
"#{(distance_in_days.to_f / 30).round} months ago"
|
123
|
+
else
|
124
|
+
"more than a year ago"
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|