cocoapods 0.5.1 → 0.6.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/CHANGELOG.md +229 -2
  2. data/README.md +50 -20
  3. data/bin/pod +3 -2
  4. data/lib/cocoapods.rb +23 -9
  5. data/lib/cocoapods/command.rb +71 -30
  6. data/lib/cocoapods/command/error_report.rb +102 -0
  7. data/lib/cocoapods/command/install.rb +27 -19
  8. data/lib/cocoapods/command/list.rb +51 -8
  9. data/lib/cocoapods/command/presenter.rb +61 -0
  10. data/lib/cocoapods/command/presenter/cocoa_pod.rb +123 -0
  11. data/lib/cocoapods/command/push.rb +102 -0
  12. data/lib/cocoapods/command/repo.rb +70 -14
  13. data/lib/cocoapods/command/search.rb +7 -10
  14. data/lib/cocoapods/command/setup.rb +76 -15
  15. data/lib/cocoapods/command/spec.rb +581 -97
  16. data/lib/cocoapods/config.rb +23 -26
  17. data/lib/cocoapods/dependency.rb +86 -40
  18. data/lib/cocoapods/downloader.rb +30 -18
  19. data/lib/cocoapods/downloader/git.rb +125 -15
  20. data/lib/cocoapods/downloader/http.rb +73 -0
  21. data/lib/cocoapods/downloader/mercurial.rb +3 -9
  22. data/lib/cocoapods/downloader/subversion.rb +3 -9
  23. data/lib/cocoapods/executable.rb +26 -3
  24. data/lib/cocoapods/generator/acknowledgements.rb +37 -0
  25. data/lib/cocoapods/generator/acknowledgements/markdown.rb +38 -0
  26. data/lib/cocoapods/generator/acknowledgements/plist.rb +63 -0
  27. data/lib/cocoapods/generator/copy_resources_script.rb +8 -4
  28. data/lib/cocoapods/generator/documentation.rb +99 -0
  29. data/lib/cocoapods/generator/dummy_source.rb +14 -0
  30. data/lib/cocoapods/installer.rb +140 -109
  31. data/lib/cocoapods/installer/target_installer.rb +78 -83
  32. data/lib/cocoapods/installer/user_project_integrator.rb +162 -0
  33. data/lib/cocoapods/local_pod.rb +240 -0
  34. data/lib/cocoapods/platform.rb +41 -18
  35. data/lib/cocoapods/podfile.rb +234 -21
  36. data/lib/cocoapods/project.rb +67 -0
  37. data/lib/cocoapods/resolver.rb +62 -32
  38. data/lib/cocoapods/sandbox.rb +63 -0
  39. data/lib/cocoapods/source.rb +42 -20
  40. data/lib/cocoapods/specification.rb +294 -271
  41. data/lib/cocoapods/specification/set.rb +10 -28
  42. data/lib/cocoapods/specification/statistics.rb +112 -0
  43. metadata +124 -11
  44. data/lib/cocoapods/xcodeproj_pods.rb +0 -111
@@ -0,0 +1,102 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rbconfig'
4
+ require 'cgi'
5
+ require 'rubygems'
6
+
7
+ module Pod
8
+ class Command
9
+ module ErrorReport
10
+ class << self
11
+ def report(error)
12
+ return <<-EOS
13
+
14
+ #{'――― MARKDOWN TEMPLATE ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――'.reversed}
15
+
16
+ ### Report
17
+
18
+ * What did you do?
19
+
20
+ * What did you expect to happen?
21
+
22
+ * What happened instead?
23
+
24
+
25
+ ### Stack
26
+
27
+ ```
28
+ CocoaPods : #{Pod::VERSION}
29
+ Ruby : #{RUBY_DESCRIPTION}
30
+ RubyGems : #{Gem::VERSION}
31
+ Host : #{host_information}
32
+ Xcode : #{xcode_information}
33
+ Ruby lib dir : #{RbConfig::CONFIG['libdir']}
34
+ Repositories : #{repo_information.join("\n ")}
35
+ ```
36
+ #{markdown_podfile}
37
+ ### Error
38
+
39
+ ```
40
+ #{error.message}
41
+ #{error.backtrace.join("\n")}
42
+ ```
43
+
44
+ #{'――― TEMPLATE END ――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――'.reversed}
45
+
46
+ #{'[!] Oh no, an error occurred.'.red}
47
+ #{error_from_podfile(error)}
48
+ #{'Search for existing github issues similar to yours:'.yellow}
49
+ #{"https://github.com/CocoaPods/CocoaPods/issues/search?q=#{CGI.escape(error.message)}"}
50
+
51
+ #{'If none exists, create a ticket, with the template displayed above, on:'.yellow}
52
+ https://github.com/CocoaPods/CocoaPods/issues/new
53
+
54
+ Don't forget to anonymize any private data!
55
+
56
+ EOS
57
+ end
58
+
59
+ private
60
+
61
+ def markdown_podfile
62
+ return '' unless Config.instance.project_podfile && Config.instance.project_podfile.exist?
63
+ <<-EOS
64
+
65
+ ### Podfile
66
+
67
+ ```ruby
68
+ #{Config.instance.project_podfile.read.strip}
69
+ ```
70
+ EOS
71
+ end
72
+
73
+ def error_from_podfile(error)
74
+ if error.message =~ /Podfile:(\d*)/
75
+ "\nIt appears to have originated from your Podfile at line #{$1}.\n"
76
+ end
77
+ end
78
+
79
+ def host_information
80
+ product, version, build =`sw_vers`.strip.split("\n").map { |line| line.split(":").last.strip }
81
+ "#{product} #{version} (#{build})"
82
+ end
83
+
84
+ def xcode_information
85
+ version, build = `xcodebuild -version`.strip.split("\n").map { |line| line.split(" ").last }
86
+ "#{version} (#{build})"
87
+ end
88
+
89
+ def repo_information
90
+ Pod::Source.all.map do |source|
91
+ repo = source.repo
92
+ Dir.chdir(repo) do
93
+ url = `git config --get remote.origin.url`.strip
94
+ sha = `git rev-parse HEAD`.strip
95
+ "#{repo.basename} - #{url} @ #{sha}"
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -4,45 +4,53 @@ module Pod
4
4
  def self.banner
5
5
  %{Installing dependencies of a project:
6
6
 
7
- $ pod install [PROJECT]
7
+ $ pod install
8
8
 
9
9
  Downloads all dependencies defined in `Podfile' and creates an Xcode
10
10
  Pods library project in `./Pods'.
11
11
 
12
- In case `PROJECT' is given, it configures it to use the specified Pods
13
- and generates a workspace with the Pods project and `PROJECT'. (It is
14
- important that once you have run this you open the workspace instead of
15
- `PROJECT'.) You usually specify `PROJECT' only the first time that you
16
- run `pod install'.
12
+ The Xcode project file should be specified in your `Podfile` like this:
13
+
14
+ xcodeproj 'path/to/XcodeProject'
15
+
16
+ If no xcodeproj is specified, then a search for an Xcode project will
17
+ be made. If more than one Xcode project is found, the command will
18
+ raise an error.
19
+
20
+ This will configure the project to reference the Pods static library,
21
+ add a build configuration file, and add a post build script to copy
22
+ Pod resources.
17
23
  }
18
24
  end
19
25
 
20
26
  def self.options
21
- " --no-clean Leave SCM dirs like `.git' and `.svn' in tact after downloading\n" +
22
- " --no-update Skip running `pod repo update` before install\n" +
23
- super
27
+ [
28
+ ["--no-clean", "Leave SCM dirs like `.git' and `.svn' intact after downloading"],
29
+ ["--no-doc", "Skip documentation generation with appledoc"],
30
+ ["--no-integrate", "Skip integration of the Pods libraries in the Xcode project(s)"],
31
+ ["--no-update", "Skip running `pod repo update` before install"],
32
+ ].concat(super)
24
33
  end
25
34
 
26
35
  def initialize(argv)
27
- config.clean = !argv.option('--no-clean')
28
- @update_repo = !argv.option('--no-update')
29
- @projpath = argv.shift_argument
36
+ config.clean = !argv.option('--no-clean')
37
+ config.generate_docs = !argv.option('--no-doc')
38
+ config.integrate_targets = !argv.option('--no-integrate')
39
+ @update_repo = !argv.option('--no-update')
30
40
  super unless argv.empty?
31
41
  end
32
42
 
33
43
  def run
34
- unless podfile = config.rootspec
44
+ unless podfile = config.podfile
35
45
  raise Informative, "No `Podfile' found in the current working directory."
36
46
  end
37
- if @projpath && !File.exist?(@projpath)
38
- raise Informative, "The specified project `#{@projpath}' does not exist."
39
- end
47
+
40
48
  if @update_repo
49
+ print_title 'Updating Spec Repositories', true
41
50
  Repo.new(ARGV.new(["update"])).run
42
51
  end
43
- installer = Installer.new(podfile)
44
- installer.install!
45
- installer.configure_project(@projpath) if @projpath
52
+
53
+ Installer.new(podfile).install!
46
54
  end
47
55
  end
48
56
  end
@@ -2,24 +2,67 @@ module Pod
2
2
  class Command
3
3
  class List < Command
4
4
  def self.banner
5
- %{List all pods:
5
+ %{List all pods:
6
6
 
7
7
  $ pod list
8
8
 
9
- Lists all available pods.}
9
+ Lists all available pods.
10
+
11
+ $ pod list new
12
+
13
+ Lists the pods introduced in the master repository since the last check.}
14
+ end
15
+
16
+ def self.options
17
+ [["--update", "Run `pod repo update` before listing"]].concat(Presenter.options).concat(super)
10
18
  end
11
19
 
20
+ extend Executable
21
+ executable :git
22
+
12
23
  def initialize(argv)
24
+ @update = argv.option('--update')
25
+ @new = argv.option('new')
26
+ @presenter = Presenter.new(argv)
27
+ super unless argv.empty?
13
28
  end
14
29
 
15
- def run
16
- Source.all.each do |source|
17
- source.pod_sets.each do |set|
18
- puts "==> #{set.name} (#{set.versions.reverse.join(", ")})"
19
- puts " #{set.specification.summary.strip}"
20
- puts
30
+ def list_all
31
+ sets = Source.all_sets
32
+ sets.each {|s| puts @presenter.describe(s)}
33
+ puts "\n#{sets.count} pods were found"
34
+ end
35
+
36
+ def list_new
37
+ days = [1,2,3,5,8]
38
+ dates, groups = {}, {}
39
+ days.each {|d| dates[d] = Time.now - 60 * 60 * 24 * d}
40
+ sets = Source.all_sets
41
+ creation_dates = Pod::Specification::Statistics.instance.creation_dates(sets)
42
+
43
+ sets.each do |set|
44
+ set_date = creation_dates[set.name]
45
+ days.each do |d|
46
+ if set_date >= dates[d]
47
+ groups[d] = [] unless groups[d]
48
+ groups[d] << set
49
+ break
50
+ end
21
51
  end
22
52
  end
53
+ days.reverse.each do |d|
54
+ sets = groups[d]
55
+ next unless sets
56
+ puts "\nPods added in the last #{d == 1 ? 'day' : "#{d} days"}".yellow
57
+ sets.sort_by {|s| creation_dates[s.name]}.each {|s| puts @presenter.describe(s)}
58
+ end
59
+ end
60
+
61
+ def run
62
+ puts "\nUpdating Spec Repositories\n".yellow if @update && config.verbose?
63
+ Repo.new(ARGV.new(["update"])).run if @update
64
+ @new ? list_new : list_all
65
+ puts
23
66
  end
24
67
  end
25
68
  end
@@ -0,0 +1,61 @@
1
+ module Pod
2
+ class Command
3
+ class Presenter
4
+ def self.options
5
+ [["--stats", "Show additional stats (like GitHub watchers and forks)"]]
6
+ end
7
+
8
+ autoload :CocoaPod, 'cocoapods/command/presenter/cocoa_pod'
9
+
10
+ def initialize(argv)
11
+ @stats = argv.option('--stats')
12
+ end
13
+
14
+ def render(array)
15
+ result = "\n"
16
+ seats.each {|s| puts describe(s)}
17
+ result
18
+ end
19
+
20
+ def describe(set)
21
+ pod = CocoaPod.new(set)
22
+ result = "\n--> #{pod.name} (#{pod.versions})\n".green
23
+ result << wrap_string(pod.summary)
24
+ result << detail('Homepage', pod.homepage)
25
+ result << detail('Source', pod.source_url)
26
+ if @stats
27
+ result << detail('Pushed', pod.github_last_activity)
28
+ result << detail('Authors', pod.authors) if pod.authors =~ /,/
29
+ result << detail('Author', pod.authors) if pod.authors !~ /,/
30
+ result << detail('License', pod.license)
31
+ result << detail('Platform', pod.platform)
32
+ result << detail('Watchers', pod.github_watchers)
33
+ result << detail('Forks', pod.github_forks)
34
+ end
35
+ result << detail('Sub specs', pod.subspecs)
36
+ result
37
+ end
38
+
39
+ private
40
+
41
+ # adapted from http://blog.macromates.com/2006/wrapping-text-with-regular-expressions/
42
+ def wrap_string(txt, col = 80, indentation = 4)
43
+ indent = ' ' * indentation
44
+ txt.strip.gsub(/(.{1,#{col}})( +|$)\n?|(.{#{col}})/, indent + "\\1\\3\n")
45
+ end
46
+
47
+ def detail(title, value)
48
+ return '' if !value
49
+ ''.tap do |t|
50
+ t << " - #{title}:".ljust(16)
51
+ if value.class == Array
52
+ separator = "\n - "
53
+ t << separator << value.join(separator)
54
+ else
55
+ t << value.to_s << "\n"
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,123 @@
1
+ module Pod
2
+ class Command
3
+ class Presenter
4
+ class CocoaPod
5
+ attr_accessor :set
6
+
7
+ def initialize(set)
8
+ @set = set
9
+ end
10
+
11
+ # set information
12
+ def name
13
+ @set.name
14
+ end
15
+
16
+ def version
17
+ @set.versions.last
18
+ end
19
+
20
+ def versions
21
+ @set.versions.reverse.join(", ")
22
+ end
23
+
24
+ # specification information
25
+ def spec
26
+ @set.specification
27
+ end
28
+
29
+ def authors
30
+ oxfordify spec.authors.keys
31
+ end
32
+
33
+ def homepage
34
+ spec.homepage
35
+ end
36
+
37
+ def description
38
+ spec.description
39
+ end
40
+
41
+ def summary
42
+ spec.summary
43
+ end
44
+
45
+ def source_url
46
+ spec.source.reject {|k,_| k == :commit || k == :tag }.values.first
47
+ end
48
+
49
+ def platform
50
+ spec.available_platforms.sort { |a,b| a.to_s.downcase <=> b.to_s.downcase }.join(' - ')
51
+ end
52
+
53
+ def license
54
+ spec.license[:type] if spec.license
55
+ end
56
+
57
+ # will return array of all subspecs (recursevly) or nil
58
+ def subspecs
59
+ (spec.recursive_subspecs.any? && spec.recursive_subspecs) || nil
60
+ end
61
+
62
+ # Statistics information
63
+ def creation_date
64
+ Pod::Specification::Statistics.instance.creation_date(@set)
65
+ end
66
+
67
+ def github_watchers
68
+ Pod::Specification::Statistics.instance.github_watchers(@set)
69
+ end
70
+
71
+ def github_forks
72
+ Pod::Specification::Statistics.instance.github_forks(@set)
73
+ end
74
+
75
+ def github_last_activity
76
+ distance_from_now_in_words(Pod::Specification::Statistics.instance.github_pushed_at(@set))
77
+ end
78
+
79
+ def ==(other)
80
+ self.class === other && @set == other.set
81
+ end
82
+
83
+ def eql?(other)
84
+ self.class === other && name.eql?(other.name)
85
+ end
86
+
87
+ def hash
88
+ name.hash
89
+ end
90
+
91
+ private
92
+ def oxfordify words
93
+ if words.size < 3
94
+ words.join ' and '
95
+ else
96
+ "#{words[0..-2].join(', ')}, and #{words.last}"
97
+ end
98
+ end
99
+
100
+ def distance_from_now_in_words(from_time)
101
+ return nil unless from_time
102
+ from_time = Time.parse(from_time)
103
+ to_time = Time.now
104
+ distance_in_days = (((to_time - from_time).abs)/60/60/24).round
105
+
106
+ case distance_in_days
107
+ when 0..7
108
+ "less than a week ago"
109
+ when 8..29
110
+ "#{distance_in_days} days ago"
111
+ when 30..45
112
+ "1 month ago"
113
+ when 46..365
114
+ "#{(distance_in_days.to_f / 30).round} months ago"
115
+ else
116
+ "more than a year ago"
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
122
+ end
123
+
@@ -0,0 +1,102 @@
1
+ require 'fileutils'
2
+
3
+ module Pod
4
+ class Command
5
+ class Push < Command
6
+ def self.banner
7
+ %{Pushing new specifications to a spec-repo:
8
+
9
+ $ pod push [REPO]
10
+
11
+ Validates `*.podspec' in the current working dir, updates
12
+ the local copy of the repository named REPO, adds specifications
13
+ to REPO, and finally it pushes REPO to its remote.}
14
+ end
15
+
16
+ def self.options
17
+ [["--allow-warnings", "Allows to push if warnings are not evitable"]].concat(super)
18
+ end
19
+
20
+ extend Executable
21
+ executable :git
22
+
23
+ def initialize(argv)
24
+ @allow_warnings = argv.option('--allow-warnings')
25
+ @repo = argv.shift_argument
26
+ super unless argv.empty? && @repo
27
+ end
28
+
29
+ def run
30
+ validate_podspec_files
31
+ check_repo_status
32
+ update_repo
33
+ add_specs_to_repo
34
+ push_repo
35
+ puts
36
+ end
37
+
38
+ private
39
+
40
+ def update_repo
41
+ puts "Updating the `#{@repo}' repo\n".yellow unless config.silent
42
+ # show the output of git even if not verbose
43
+ Dir.chdir(repo_dir) { puts `git pull 2>&1` }
44
+ end
45
+
46
+ def push_repo
47
+ puts "\nPushing the `#{@repo}' repo\n".yellow unless config.silent
48
+ Dir.chdir(repo_dir) { puts `git push 2>&1` }
49
+ end
50
+
51
+ def repo_dir
52
+ dir = config.repos_dir + @repo
53
+ raise Informative, "[!] `#{@repo}' repo not found".red unless dir.exist?
54
+ dir
55
+ end
56
+
57
+ def check_repo_status
58
+ # TODO: add specs for staged and unstaged files (tested manually)
59
+ clean = Dir.chdir(repo_dir) { `git status --porcelain 2>&1` } == ''
60
+ raise Informative, "[!] `#{@repo}' repo not clean".red unless clean
61
+ end
62
+
63
+ def podspec_files
64
+ files = Pathname.glob("*.podspec")
65
+ raise Informative, "[!] Couldn't find .podspec file in current directory".red if files.empty?
66
+ files
67
+ end
68
+
69
+ def validate_podspec_files
70
+ puts "\nValidating specs".yellow unless config.silent
71
+ lint_argv = ["lint"]
72
+ lint_argv << "--only-errors" if @allow_warnings
73
+ lint_argv << "--silent" if config.silent
74
+ all_valid = Spec.new(ARGV.new(lint_argv)).run
75
+ end
76
+
77
+ def add_specs_to_repo
78
+ puts "\nAdding the specs to the #{@repo} repo\n".yellow unless config.silent
79
+ podspec_files.each do |spec_file|
80
+ spec = Pod::Specification.from_file(spec_file)
81
+ output_path = File.join(repo_dir, spec.name, spec.version.to_s)
82
+ if Pathname.new(output_path).exist?
83
+ message = "[Fix] #{spec}"
84
+ elsif Pathname.new(File.join(repo_dir, spec.name)).exist?
85
+ message = "[Update] #{spec}"
86
+ else
87
+ message = "[Add] #{spec}"
88
+ end
89
+ puts " - #{message}" unless config.silent
90
+
91
+ FileUtils.mkdir_p(output_path)
92
+ FileUtils.cp(Pathname.new(spec.name+'.podspec'), output_path)
93
+ Dir.chdir(repo_dir) do
94
+ git("add #{spec.name}")
95
+ # Bypass the pre-commit hook because we already performed validation
96
+ git("commit --no-verify -m '#{message}'")
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end