cocoapods 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
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
+