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.
Files changed (37) hide show
  1. data/CHANGELOG.md +42 -9
  2. data/bin/pod +4 -0
  3. data/lib/cocoapods.rb +2 -2
  4. data/lib/cocoapods/command.rb +2 -25
  5. data/lib/cocoapods/command/install.rb +1 -2
  6. data/lib/cocoapods/command/linter.rb +24 -4
  7. data/lib/cocoapods/command/list.rb +16 -11
  8. data/lib/cocoapods/command/outdated.rb +2 -4
  9. data/lib/cocoapods/command/push.rb +10 -10
  10. data/lib/cocoapods/command/repo.rb +22 -20
  11. data/lib/cocoapods/command/search.rb +6 -4
  12. data/lib/cocoapods/command/setup.rb +15 -14
  13. data/lib/cocoapods/command/spec.rb +17 -14
  14. data/lib/cocoapods/command/update.rb +9 -1
  15. data/lib/cocoapods/config.rb +12 -4
  16. data/lib/cocoapods/dependency.rb +18 -15
  17. data/lib/cocoapods/downloader/git.rb +41 -28
  18. data/lib/cocoapods/downloader/http.rb +10 -3
  19. data/lib/cocoapods/downloader/mercurial.rb +6 -4
  20. data/lib/cocoapods/downloader/subversion.rb +6 -2
  21. data/lib/cocoapods/executable.rb +6 -6
  22. data/lib/cocoapods/generator/acknowledgements/markdown.rb +1 -0
  23. data/lib/cocoapods/installer.rb +73 -57
  24. data/lib/cocoapods/installer/target_installer.rb +15 -11
  25. data/lib/cocoapods/installer/user_project_integrator.rb +25 -16
  26. data/lib/cocoapods/local_pod.rb +34 -12
  27. data/lib/cocoapods/podfile.rb +15 -0
  28. data/lib/cocoapods/resolver.rb +33 -39
  29. data/lib/cocoapods/source.rb +172 -48
  30. data/lib/cocoapods/specification.rb +48 -32
  31. data/lib/cocoapods/specification/set.rb +102 -34
  32. data/lib/cocoapods/specification/statistics.rb +1 -1
  33. data/lib/cocoapods/user_interface.rb +215 -0
  34. data/lib/cocoapods/user_interface/ui_pod.rb +130 -0
  35. metadata +7 -7
  36. data/lib/cocoapods/command/presenter.rb +0 -61
  37. 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
- def initialize(pod_dir)
9
- @pod_dir = pod_dir
10
- @required_by = []
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 << dependent_name
56
+ @required_by << dependent_name
22
57
  @dependencies << dependency
23
58
  end
24
59
 
25
- def specification_by_name(name)
26
- specification.top_level_parent.subspec_by_name(name)
27
- end
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
- def name
36
- @pod_dir.basename.to_s
37
- end
38
-
39
- def specification_path
40
- @pod_dir + required_version.to_s + "#{name}.podspec"
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 ||= Specification.from_file(specification_path)
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
- # Return the first version that matches the current dependency.
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 && @pod_dir == other.pod_dir
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 `#{@pod_dir}'>"
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
- # Returns Pod::Version instances, for each version directory, sorted from
63
- # highest version to lowest.
64
- def versions
65
- @pod_dir.children.map do |v|
66
- basename = v.basename.to_s
67
- Version.new(basename) if v.directory? && basename[0,1] != '.'
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 && name == other.name
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.pod_dir.dirname) do
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
+