cocoapods 0.14.0 → 0.15.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.
- 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
|
+
|