carat 1.9.9.pre1

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 (184) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +16 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +24 -0
  5. data/CHANGELOG.md +2006 -0
  6. data/CODE_OF_CONDUCT.md +40 -0
  7. data/CONTRIBUTING.md +23 -0
  8. data/DEVELOPMENT.md +119 -0
  9. data/ISSUES.md +96 -0
  10. data/LICENSE.md +23 -0
  11. data/README.md +32 -0
  12. data/Rakefile +308 -0
  13. data/bin/carat +21 -0
  14. data/bin/carat_ruby +56 -0
  15. data/carat.gemspec +32 -0
  16. data/lib/carat.rb +446 -0
  17. data/lib/carat/anonymizable_uri.rb +32 -0
  18. data/lib/carat/capistrano.rb +16 -0
  19. data/lib/carat/cli.rb +407 -0
  20. data/lib/carat/cli/binstubs.rb +38 -0
  21. data/lib/carat/cli/cache.rb +35 -0
  22. data/lib/carat/cli/check.rb +35 -0
  23. data/lib/carat/cli/clean.rb +26 -0
  24. data/lib/carat/cli/common.rb +56 -0
  25. data/lib/carat/cli/config.rb +84 -0
  26. data/lib/carat/cli/console.rb +38 -0
  27. data/lib/carat/cli/exec.rb +44 -0
  28. data/lib/carat/cli/gem.rb +195 -0
  29. data/lib/carat/cli/init.rb +33 -0
  30. data/lib/carat/cli/inject.rb +33 -0
  31. data/lib/carat/cli/install.rb +156 -0
  32. data/lib/carat/cli/open.rb +23 -0
  33. data/lib/carat/cli/outdated.rb +80 -0
  34. data/lib/carat/cli/package.rb +45 -0
  35. data/lib/carat/cli/platform.rb +43 -0
  36. data/lib/carat/cli/show.rb +74 -0
  37. data/lib/carat/cli/update.rb +73 -0
  38. data/lib/carat/cli/viz.rb +27 -0
  39. data/lib/carat/constants.rb +5 -0
  40. data/lib/carat/current_ruby.rb +183 -0
  41. data/lib/carat/definition.rb +628 -0
  42. data/lib/carat/dep_proxy.rb +43 -0
  43. data/lib/carat/dependency.rb +110 -0
  44. data/lib/carat/deployment.rb +59 -0
  45. data/lib/carat/deprecate.rb +15 -0
  46. data/lib/carat/dsl.rb +331 -0
  47. data/lib/carat/endpoint_specification.rb +76 -0
  48. data/lib/carat/env.rb +75 -0
  49. data/lib/carat/environment.rb +42 -0
  50. data/lib/carat/fetcher.rb +423 -0
  51. data/lib/carat/friendly_errors.rb +85 -0
  52. data/lib/carat/gem_helper.rb +180 -0
  53. data/lib/carat/gem_helpers.rb +26 -0
  54. data/lib/carat/gem_installer.rb +9 -0
  55. data/lib/carat/gem_path_manipulation.rb +8 -0
  56. data/lib/carat/gem_tasks.rb +2 -0
  57. data/lib/carat/graph.rb +169 -0
  58. data/lib/carat/index.rb +197 -0
  59. data/lib/carat/injector.rb +64 -0
  60. data/lib/carat/installer.rb +339 -0
  61. data/lib/carat/lazy_specification.rb +83 -0
  62. data/lib/carat/lockfile_parser.rb +167 -0
  63. data/lib/carat/match_platform.rb +13 -0
  64. data/lib/carat/psyched_yaml.rb +26 -0
  65. data/lib/carat/remote_specification.rb +57 -0
  66. data/lib/carat/resolver.rb +334 -0
  67. data/lib/carat/retry.rb +60 -0
  68. data/lib/carat/ruby_dsl.rb +11 -0
  69. data/lib/carat/ruby_version.rb +117 -0
  70. data/lib/carat/rubygems_ext.rb +170 -0
  71. data/lib/carat/rubygems_integration.rb +619 -0
  72. data/lib/carat/runtime.rb +289 -0
  73. data/lib/carat/settings.rb +208 -0
  74. data/lib/carat/setup.rb +24 -0
  75. data/lib/carat/shared_helpers.rb +149 -0
  76. data/lib/carat/similarity_detector.rb +63 -0
  77. data/lib/carat/source.rb +46 -0
  78. data/lib/carat/source/git.rb +294 -0
  79. data/lib/carat/source/git/git_proxy.rb +162 -0
  80. data/lib/carat/source/path.rb +226 -0
  81. data/lib/carat/source/path/installer.rb +43 -0
  82. data/lib/carat/source/rubygems.rb +381 -0
  83. data/lib/carat/source_list.rb +101 -0
  84. data/lib/carat/spec_set.rb +154 -0
  85. data/lib/carat/ssl_certs/.document +1 -0
  86. data/lib/carat/ssl_certs/AddTrustExternalCARoot-2048.pem +25 -0
  87. data/lib/carat/ssl_certs/AddTrustExternalCARoot.pem +32 -0
  88. data/lib/carat/ssl_certs/Class3PublicPrimaryCertificationAuthority.pem +14 -0
  89. data/lib/carat/ssl_certs/DigiCertHighAssuranceEVRootCA.pem +23 -0
  90. data/lib/carat/ssl_certs/EntrustnetSecureServerCertificationAuthority.pem +28 -0
  91. data/lib/carat/ssl_certs/GeoTrustGlobalCA.pem +20 -0
  92. data/lib/carat/ssl_certs/certificate_manager.rb +66 -0
  93. data/lib/carat/ssl_certs/index.rubygems.org/GlobalSignRootCA.pem +21 -0
  94. data/lib/carat/ssl_certs/rubygems.global.ssl.fastly.net/DigiCertHighAssuranceEVRootCA.pem +23 -0
  95. data/lib/carat/ssl_certs/rubygems.org/AddTrustExternalCARoot.pem +25 -0
  96. data/lib/carat/templates/Executable +16 -0
  97. data/lib/carat/templates/Executable.standalone +12 -0
  98. data/lib/carat/templates/Gemfile +4 -0
  99. data/lib/carat/templates/newgem/.travis.yml.tt +3 -0
  100. data/lib/carat/templates/newgem/CODE_OF_CONDUCT.md.tt +13 -0
  101. data/lib/carat/templates/newgem/Gemfile.tt +4 -0
  102. data/lib/carat/templates/newgem/LICENSE.txt.tt +21 -0
  103. data/lib/carat/templates/newgem/README.md.tt +39 -0
  104. data/lib/carat/templates/newgem/Rakefile.tt +25 -0
  105. data/lib/carat/templates/newgem/bin/console.tt +14 -0
  106. data/lib/carat/templates/newgem/bin/setup.tt +7 -0
  107. data/lib/carat/templates/newgem/exe/newgem.tt +3 -0
  108. data/lib/carat/templates/newgem/ext/newgem/extconf.rb.tt +3 -0
  109. data/lib/carat/templates/newgem/ext/newgem/newgem.c.tt +9 -0
  110. data/lib/carat/templates/newgem/ext/newgem/newgem.h.tt +6 -0
  111. data/lib/carat/templates/newgem/gitignore.tt +16 -0
  112. data/lib/carat/templates/newgem/lib/newgem.rb.tt +12 -0
  113. data/lib/carat/templates/newgem/lib/newgem/version.rb.tt +7 -0
  114. data/lib/carat/templates/newgem/newgem.gemspec.tt +43 -0
  115. data/lib/carat/templates/newgem/rspec.tt +2 -0
  116. data/lib/carat/templates/newgem/spec/newgem_spec.rb.tt +11 -0
  117. data/lib/carat/templates/newgem/spec/spec_helper.rb.tt +2 -0
  118. data/lib/carat/templates/newgem/test/minitest_helper.rb.tt +4 -0
  119. data/lib/carat/templates/newgem/test/test_newgem.rb.tt +11 -0
  120. data/lib/carat/ui.rb +7 -0
  121. data/lib/carat/ui/rg_proxy.rb +21 -0
  122. data/lib/carat/ui/shell.rb +103 -0
  123. data/lib/carat/ui/silent.rb +44 -0
  124. data/lib/carat/vendor/molinillo/lib/molinillo.rb +5 -0
  125. data/lib/carat/vendor/molinillo/lib/molinillo/dependency_graph.rb +266 -0
  126. data/lib/carat/vendor/molinillo/lib/molinillo/errors.rb +69 -0
  127. data/lib/carat/vendor/molinillo/lib/molinillo/gem_metadata.rb +3 -0
  128. data/lib/carat/vendor/molinillo/lib/molinillo/modules/specification_provider.rb +90 -0
  129. data/lib/carat/vendor/molinillo/lib/molinillo/modules/ui.rb +63 -0
  130. data/lib/carat/vendor/molinillo/lib/molinillo/resolution.rb +415 -0
  131. data/lib/carat/vendor/molinillo/lib/molinillo/resolver.rb +43 -0
  132. data/lib/carat/vendor/molinillo/lib/molinillo/state.rb +43 -0
  133. data/lib/carat/vendor/net/http/faster.rb +26 -0
  134. data/lib/carat/vendor/net/http/persistent.rb +1230 -0
  135. data/lib/carat/vendor/net/http/persistent/ssl_reuse.rb +128 -0
  136. data/lib/carat/vendor/thor/lib/thor.rb +484 -0
  137. data/lib/carat/vendor/thor/lib/thor/actions.rb +319 -0
  138. data/lib/carat/vendor/thor/lib/thor/actions/create_file.rb +103 -0
  139. data/lib/carat/vendor/thor/lib/thor/actions/create_link.rb +59 -0
  140. data/lib/carat/vendor/thor/lib/thor/actions/directory.rb +118 -0
  141. data/lib/carat/vendor/thor/lib/thor/actions/empty_directory.rb +135 -0
  142. data/lib/carat/vendor/thor/lib/thor/actions/file_manipulation.rb +316 -0
  143. data/lib/carat/vendor/thor/lib/thor/actions/inject_into_file.rb +107 -0
  144. data/lib/carat/vendor/thor/lib/thor/base.rb +656 -0
  145. data/lib/carat/vendor/thor/lib/thor/command.rb +133 -0
  146. data/lib/carat/vendor/thor/lib/thor/core_ext/hash_with_indifferent_access.rb +77 -0
  147. data/lib/carat/vendor/thor/lib/thor/core_ext/io_binary_read.rb +10 -0
  148. data/lib/carat/vendor/thor/lib/thor/core_ext/ordered_hash.rb +98 -0
  149. data/lib/carat/vendor/thor/lib/thor/error.rb +32 -0
  150. data/lib/carat/vendor/thor/lib/thor/group.rb +281 -0
  151. data/lib/carat/vendor/thor/lib/thor/invocation.rb +178 -0
  152. data/lib/carat/vendor/thor/lib/thor/line_editor.rb +17 -0
  153. data/lib/carat/vendor/thor/lib/thor/line_editor/basic.rb +35 -0
  154. data/lib/carat/vendor/thor/lib/thor/line_editor/readline.rb +88 -0
  155. data/lib/carat/vendor/thor/lib/thor/parser.rb +4 -0
  156. data/lib/carat/vendor/thor/lib/thor/parser/argument.rb +73 -0
  157. data/lib/carat/vendor/thor/lib/thor/parser/arguments.rb +175 -0
  158. data/lib/carat/vendor/thor/lib/thor/parser/option.rb +125 -0
  159. data/lib/carat/vendor/thor/lib/thor/parser/options.rb +218 -0
  160. data/lib/carat/vendor/thor/lib/thor/rake_compat.rb +71 -0
  161. data/lib/carat/vendor/thor/lib/thor/runner.rb +322 -0
  162. data/lib/carat/vendor/thor/lib/thor/shell.rb +81 -0
  163. data/lib/carat/vendor/thor/lib/thor/shell/basic.rb +421 -0
  164. data/lib/carat/vendor/thor/lib/thor/shell/color.rb +149 -0
  165. data/lib/carat/vendor/thor/lib/thor/shell/html.rb +126 -0
  166. data/lib/carat/vendor/thor/lib/thor/util.rb +267 -0
  167. data/lib/carat/vendor/thor/lib/thor/version.rb +3 -0
  168. data/lib/carat/vendored_fileutils.rb +9 -0
  169. data/lib/carat/vendored_molinillo.rb +2 -0
  170. data/lib/carat/vendored_persistent.rb +11 -0
  171. data/lib/carat/vendored_thor.rb +3 -0
  172. data/lib/carat/version.rb +6 -0
  173. data/lib/carat/vlad.rb +11 -0
  174. data/lib/carat/worker.rb +73 -0
  175. data/man/carat-config.ronn +178 -0
  176. data/man/carat-exec.ronn +136 -0
  177. data/man/carat-install.ronn +383 -0
  178. data/man/carat-package.ronn +66 -0
  179. data/man/carat-platform.ronn +42 -0
  180. data/man/carat-update.ronn +188 -0
  181. data/man/carat.ronn +98 -0
  182. data/man/gemfile.5.ronn +473 -0
  183. data/man/index.txt +7 -0
  184. metadata +321 -0
@@ -0,0 +1,43 @@
1
+ module Carat
2
+ class DepProxy
3
+
4
+ attr_reader :__platform, :dep
5
+
6
+ def initialize(dep, platform)
7
+ @dep, @__platform = dep, platform
8
+ end
9
+
10
+ def hash
11
+ @hash ||= dep.hash
12
+ end
13
+
14
+ def ==(o)
15
+ dep == o.dep && __platform == o.__platform
16
+ end
17
+
18
+ alias eql? ==
19
+
20
+ def type
21
+ @dep.type
22
+ end
23
+
24
+ def name
25
+ @dep.name
26
+ end
27
+
28
+ def requirement
29
+ @dep.requirement
30
+ end
31
+
32
+ def to_s
33
+ "#{name} (#{requirement}) #{__platform}"
34
+ end
35
+
36
+ private
37
+
38
+ def method_missing(*args)
39
+ @dep.send(*args)
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,110 @@
1
+ require 'rubygems/dependency'
2
+ require 'carat/shared_helpers'
3
+ require 'carat/rubygems_ext'
4
+
5
+ module Carat
6
+ class Dependency < Gem::Dependency
7
+ attr_reader :autorequire
8
+ attr_reader :groups
9
+ attr_reader :platforms
10
+
11
+ PLATFORM_MAP = {
12
+ :ruby => Gem::Platform::RUBY,
13
+ :ruby_18 => Gem::Platform::RUBY,
14
+ :ruby_19 => Gem::Platform::RUBY,
15
+ :ruby_20 => Gem::Platform::RUBY,
16
+ :ruby_21 => Gem::Platform::RUBY,
17
+ :ruby_22 => Gem::Platform::RUBY,
18
+ :mri => Gem::Platform::RUBY,
19
+ :mri_18 => Gem::Platform::RUBY,
20
+ :mri_19 => Gem::Platform::RUBY,
21
+ :mri_20 => Gem::Platform::RUBY,
22
+ :mri_21 => Gem::Platform::RUBY,
23
+ :mri_22 => Gem::Platform::RUBY,
24
+ :rbx => Gem::Platform::RUBY,
25
+ :jruby => Gem::Platform::JAVA,
26
+ :jruby_18 => Gem::Platform::JAVA,
27
+ :jruby_19 => Gem::Platform::JAVA,
28
+ :mswin => Gem::Platform::MSWIN,
29
+ :mswin_18 => Gem::Platform::MSWIN,
30
+ :mswin_19 => Gem::Platform::MSWIN,
31
+ :mswin_20 => Gem::Platform::MSWIN,
32
+ :mswin_21 => Gem::Platform::MSWIN,
33
+ :mswin64 => Gem::Platform::MSWIN64,
34
+ :mswin64_19 => Gem::Platform::MSWIN64,
35
+ :mswin64_20 => Gem::Platform::MSWIN64,
36
+ :mswin64_21 => Gem::Platform::MSWIN64,
37
+ :mingw => Gem::Platform::MINGW,
38
+ :mingw_18 => Gem::Platform::MINGW,
39
+ :mingw_19 => Gem::Platform::MINGW,
40
+ :mingw_20 => Gem::Platform::MINGW,
41
+ :mingw_21 => Gem::Platform::MINGW,
42
+ :mingw_22 => Gem::Platform::MINGW,
43
+ :x64_mingw => Gem::Platform::X64_MINGW,
44
+ :x64_mingw_20 => Gem::Platform::X64_MINGW,
45
+ :x64_mingw_21 => Gem::Platform::X64_MINGW,
46
+ :x64_mingw_22 => Gem::Platform::X64_MINGW
47
+ }.freeze
48
+
49
+ def initialize(name, version, options = {}, &blk)
50
+ type = options["type"] || :runtime
51
+ super(name, version, type)
52
+
53
+ @autorequire = nil
54
+ @groups = Array(options["group"] || :default).map { |g| g.to_sym }
55
+ @source = options["source"]
56
+ @platforms = Array(options["platforms"])
57
+ @env = options["env"]
58
+
59
+ if options.key?('require')
60
+ @autorequire = Array(options['require'] || [])
61
+ end
62
+ end
63
+
64
+ def gem_platforms(valid_platforms)
65
+ return valid_platforms if @platforms.empty?
66
+
67
+ platforms = []
68
+ @platforms.each do |p|
69
+ platform = PLATFORM_MAP[p]
70
+ next unless valid_platforms.include?(platform)
71
+ platforms |= [platform]
72
+ end
73
+ platforms
74
+ end
75
+
76
+ def should_include?
77
+ current_env? && current_platform?
78
+ end
79
+
80
+ def current_env?
81
+ return true unless @env
82
+ if @env.is_a?(Hash)
83
+ @env.all? do |key, val|
84
+ ENV[key.to_s] && (val.is_a?(String) ? ENV[key.to_s] == val : ENV[key.to_s] =~ val)
85
+ end
86
+ else
87
+ ENV[@env.to_s]
88
+ end
89
+ end
90
+
91
+ def current_platform?
92
+ return true if @platforms.empty?
93
+ @platforms.any? { |p|
94
+ Carat.current_ruby.send("#{p}?")
95
+ }
96
+ end
97
+
98
+ def to_lock
99
+ out = super
100
+ out << '!' if source
101
+ out << "\n"
102
+ end
103
+
104
+ def specific?
105
+ super
106
+ rescue NoMethodError
107
+ requirement != ">= 0"
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,59 @@
1
+ module Carat
2
+ class Deployment
3
+ def self.define_task(context, task_method = :task, opts = {})
4
+ if defined?(Capistrano) && context.is_a?(Capistrano::Configuration)
5
+ context_name = "capistrano"
6
+ role_default = "{:except => {:no_release => true}}"
7
+ error_type = ::Capistrano::CommandError
8
+ else
9
+ context_name = "vlad"
10
+ role_default = "[:app]"
11
+ error_type = ::Rake::CommandFailedError
12
+ end
13
+
14
+ roles = context.fetch(:bundle_roles, false)
15
+ opts[:roles] = roles if roles
16
+
17
+ context.send :namespace, :bundle do
18
+ send :desc, <<-DESC
19
+ Install the current Carat environment. By default, gems will be \
20
+ installed to the shared/bundle path. Gems in the development and \
21
+ test group will not be installed. The install command is executed \
22
+ with the --deployment and --quiet flags. If the bundle cmd cannot \
23
+ be found then you can override the bundle_cmd variable to specify \
24
+ which one it should use. The base path to the app is fetched from \
25
+ the :latest_release variable. Set it for custom deploy layouts.
26
+
27
+ You can override any of these defaults by setting the variables shown below.
28
+
29
+ N.B. bundle_roles must be defined before you require 'carat/#{context_name}' \
30
+ in your deploy.rb file.
31
+
32
+ set :bundle_gemfile, "Gemfile"
33
+ set :bundle_dir, File.join(fetch(:shared_path), 'carat')
34
+ set :bundle_flags, "--deployment --quiet"
35
+ set :bundle_without, [:development, :test]
36
+ set :bundle_cmd, "carat" # e.g. "/opt/ruby/bin/carat"
37
+ set :bundle_roles, #{role_default} # e.g. [:app, :batch]
38
+ DESC
39
+ send task_method, :install, opts do
40
+ bundle_cmd = context.fetch(:bundle_cmd, "carat")
41
+ bundle_flags = context.fetch(:bundle_flags, "--deployment --quiet")
42
+ bundle_dir = context.fetch(:bundle_dir, File.join(context.fetch(:shared_path), 'carat'))
43
+ bundle_gemfile = context.fetch(:bundle_gemfile, "Gemfile")
44
+ bundle_without = [*context.fetch(:bundle_without, [:development, :test])].compact
45
+ app_path = context.fetch(:latest_release)
46
+ if app_path.to_s.empty?
47
+ raise error_type.new("Cannot detect current release path - make sure you have deployed at least once.")
48
+ end
49
+ args = ["--gemfile #{File.join(app_path, bundle_gemfile)}"]
50
+ args << "--path #{bundle_dir}" unless bundle_dir.to_s.empty?
51
+ args << bundle_flags.to_s
52
+ args << "--without #{bundle_without.join(" ")}" unless bundle_without.empty?
53
+
54
+ run "cd #{app_path} && #{bundle_cmd} install #{args.join(' ')}"
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,15 @@
1
+ module Carat
2
+
3
+ if defined? ::Deprecate
4
+ Deprecate = ::Deprecate
5
+ elsif defined? Gem::Deprecate
6
+ Deprecate = Gem::Deprecate
7
+ else
8
+ class Deprecate; end
9
+ end
10
+
11
+ unless Deprecate.respond_to?(:skip_during)
12
+ def Deprecate.skip_during; yield; end
13
+ end
14
+
15
+ end
@@ -0,0 +1,331 @@
1
+ require 'carat/dependency'
2
+ require 'carat/ruby_dsl'
3
+
4
+ module Carat
5
+ class Dsl
6
+ include RubyDsl
7
+
8
+ def self.evaluate(gemfile, lockfile, unlock)
9
+ builder = new
10
+ builder.eval_gemfile(gemfile)
11
+ builder.to_definition(lockfile, unlock)
12
+ end
13
+
14
+ VALID_PLATFORMS = Carat::Dependency::PLATFORM_MAP.keys.freeze
15
+
16
+ attr_accessor :dependencies
17
+
18
+ def initialize
19
+ @source = nil
20
+ @sources = SourceList.new
21
+ @git_sources = {}
22
+ @dependencies = []
23
+ @groups = []
24
+ @platforms = []
25
+ @env = nil
26
+ @ruby_version = nil
27
+ add_git_sources
28
+ end
29
+
30
+ def eval_gemfile(gemfile, contents = nil)
31
+ contents ||= Carat.read_file(gemfile.to_s)
32
+ instance_eval(contents, gemfile.to_s, 1)
33
+ rescue SyntaxError => e
34
+ syntax_msg = e.message.gsub("#{gemfile}:", 'on line ')
35
+ raise GemfileError, "Gemfile syntax error #{syntax_msg}"
36
+ rescue ScriptError, RegexpError, NameError, ArgumentError, RuntimeError => e
37
+ e.backtrace[0] = "#{e.backtrace[0]}: #{e.message} (#{e.class})"
38
+ Carat.ui.warn e.backtrace.join("\n ")
39
+ raise GemfileError, "There was an error in your Gemfile," \
40
+ " and Carat cannot continue."
41
+ end
42
+
43
+ def gemspec(opts = nil)
44
+ path = opts && opts[:path] || '.'
45
+ name = opts && opts[:name] || '{,*}'
46
+ development_group = opts && opts[:development_group] || :development
47
+ expanded_path = File.expand_path(path, Carat.default_gemfile.dirname)
48
+
49
+ gemspecs = Dir[File.join(expanded_path, "#{name}.gemspec")]
50
+
51
+ case gemspecs.size
52
+ when 1
53
+ spec = Carat.load_gemspec(gemspecs.first)
54
+ raise InvalidOption, "There was an error loading the gemspec at #{gemspecs.first}." unless spec
55
+ gem spec.name, :path => path
56
+ group(development_group) do
57
+ spec.development_dependencies.each do |dep|
58
+ gem dep.name, *(dep.requirement.as_list + [:type => :development])
59
+ end
60
+ end
61
+ when 0
62
+ raise InvalidOption, "There are no gemspecs at #{expanded_path}."
63
+ else
64
+ raise InvalidOption, "There are multiple gemspecs at #{expanded_path}. Please use the :name option to specify which one."
65
+ end
66
+ end
67
+
68
+ def gem(name, *args)
69
+ options = args.last.is_a?(Hash) ? args.pop.dup : {}
70
+ version = args || [">= 0"]
71
+
72
+ normalize_options(name, version, options)
73
+
74
+ dep = Dependency.new(name, version, options)
75
+
76
+ # if there's already a dependency with this name we try to prefer one
77
+ if current = @dependencies.find { |d| d.name == dep.name }
78
+ if current.requirement != dep.requirement
79
+ if current.type == :development
80
+ @dependencies.delete current
81
+ elsif dep.type == :development
82
+ return
83
+ else
84
+ raise GemfileError, "You cannot specify the same gem twice with different version requirements.\n" \
85
+ "You specified: #{current.name} (#{current.requirement}) and #{dep.name} (#{dep.requirement})"
86
+ end
87
+
88
+ else
89
+ Carat.ui.warn "Your Gemfile lists the gem #{current.name} (#{current.requirement}) more than once.\n" \
90
+ "You should probably keep only one of them.\n" \
91
+ "While it's not a problem now, it could cause errors if you change the version of just one of them later."
92
+ end
93
+
94
+ if current.source != dep.source
95
+ if current.type == :development
96
+ @dependencies.delete current
97
+ elsif dep.type == :development
98
+ return
99
+ else
100
+ raise GemfileError, "You cannot specify the same gem twice coming from different sources.\n" \
101
+ "You specified that #{dep.name} (#{dep.requirement}) should come from " \
102
+ "#{current.source || 'an unspecified source'} and #{dep.source}\n"
103
+ end
104
+ end
105
+ end
106
+
107
+ @dependencies << dep
108
+ end
109
+
110
+ def source(source, &blk)
111
+ source = normalize_source(source)
112
+ if block_given?
113
+ with_source(@sources.add_rubygems_source("remotes" => source), &blk)
114
+ else
115
+ check_primary_source_safety(@sources)
116
+ @sources.add_rubygems_remote(source)
117
+ end
118
+ end
119
+
120
+ def git_source(name, &block)
121
+ unless block_given?
122
+ raise InvalidOption, "You need to pass a block to #git_source"
123
+ end
124
+
125
+ if valid_keys.include?(name.to_s)
126
+ raise InvalidOption, "You cannot use #{name} as a git source. It " \
127
+ "is a reserved key. Reserved keys are: #{valid_keys.join(", ")}"
128
+ end
129
+
130
+ @git_sources[name.to_s] = block
131
+ end
132
+
133
+ def path(path, options = {}, &blk)
134
+ with_source(@sources.add_path_source(normalize_hash(options).merge("path" => Pathname.new(path))), &blk)
135
+ end
136
+
137
+ def git(uri, options = {}, &blk)
138
+ unless block_given?
139
+ msg = "You can no longer specify a git source by itself. Instead, \n" \
140
+ "either use the :git option on a gem, or specify the gems that \n" \
141
+ "carat should find in the git source by passing a block to \n" \
142
+ "the git method, like: \n\n" \
143
+ " git 'git://github.com/rails/rails.git' do\n" \
144
+ " gem 'rails'\n" \
145
+ " end"
146
+ raise DeprecatedError, msg
147
+ end
148
+
149
+ with_source(@sources.add_git_source(normalize_hash(options).merge("uri" => uri)), &blk)
150
+ end
151
+
152
+ def github(repo, options = {})
153
+ raise ArgumentError, "Github sources require a block" unless block_given?
154
+ github_uri = @git_sources["github"].call(repo)
155
+ git_options = normalize_hash(options).merge("uri" => github_uri)
156
+ git_source = @sources.add_git_source(git_options)
157
+ with_source(git_source) { yield }
158
+ end
159
+
160
+ def to_definition(lockfile, unlock)
161
+ Definition.new(lockfile, @dependencies, @sources, unlock, @ruby_version)
162
+ end
163
+
164
+ def group(*args, &blk)
165
+ @groups.concat args
166
+ yield
167
+ ensure
168
+ args.each { @groups.pop }
169
+ end
170
+
171
+ def platforms(*platforms)
172
+ @platforms.concat platforms
173
+ yield
174
+ ensure
175
+ platforms.each { @platforms.pop }
176
+ end
177
+ alias_method :platform, :platforms
178
+
179
+ def env(name)
180
+ @env, old = name, @env
181
+ yield
182
+ ensure
183
+ @env = old
184
+ end
185
+
186
+ def method_missing(name, *args)
187
+ location = caller[0].split(':')[0..1].join(':')
188
+ raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile\n" \
189
+ " from #{location}"
190
+ end
191
+
192
+ private
193
+
194
+ def add_git_sources
195
+ git_source(:github) do |repo_name|
196
+ repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
197
+ "git://github.com/#{repo_name}.git"
198
+ end
199
+
200
+ git_source(:gist){ |repo_name| "https://gist.github.com/#{repo_name}.git" }
201
+
202
+ git_source(:bitbucket) do |repo_name|
203
+ user_name, repo_name = repo_name.split '/'
204
+ repo_name ||= user_name
205
+ "https://#{user_name}@bitbucket.org/#{user_name}/#{repo_name}.git"
206
+ end
207
+ end
208
+
209
+ def with_source(source)
210
+ if block_given?
211
+ @source = source
212
+ yield
213
+ end
214
+ source
215
+ ensure
216
+ @source = nil
217
+ end
218
+
219
+ def normalize_hash(opts)
220
+ opts.keys.each do |k|
221
+ opts[k.to_s] = opts.delete(k) unless k.is_a?(String)
222
+ end
223
+ opts
224
+ end
225
+
226
+ def valid_keys
227
+ @valid_keys ||= %w(group groups git path name branch ref tag require submodules platform platforms type source)
228
+ end
229
+
230
+ def normalize_options(name, version, opts)
231
+ if name.is_a?(Symbol)
232
+ raise GemfileError, %{You need to specify gem names as Strings. Use 'gem "#{name}"' instead.}
233
+ end
234
+ if name =~ /\s/
235
+ raise GemfileError, %{'#{name}' is not a valid gem name because it contains whitespace.}
236
+ end
237
+
238
+ normalize_hash(opts)
239
+
240
+ git_names = @git_sources.keys.map(&:to_s)
241
+
242
+ invalid_keys = opts.keys - (valid_keys + git_names)
243
+ if invalid_keys.any?
244
+ message = "You passed #{invalid_keys.map{|k| ':'+k }.join(", ")} "
245
+ message << if invalid_keys.size > 1
246
+ "as options for gem '#{name}', but they are invalid."
247
+ else
248
+ "as an option for gem '#{name}', but it is invalid."
249
+ end
250
+
251
+ message << " Valid options are: #{valid_keys.join(", ")}"
252
+ raise InvalidOption, message
253
+ end
254
+
255
+ groups = @groups.dup
256
+ opts["group"] = opts.delete("groups") || opts["group"]
257
+ groups.concat Array(opts.delete("group"))
258
+ groups = [:default] if groups.empty?
259
+
260
+ platforms = @platforms.dup
261
+ opts["platforms"] = opts["platform"] || opts["platforms"]
262
+ platforms.concat Array(opts.delete("platforms"))
263
+ platforms.map! { |p| p.to_sym }
264
+ platforms.each do |p|
265
+ next if VALID_PLATFORMS.include?(p)
266
+ raise GemfileError, "`#{p}` is not a valid platform. The available options are: #{VALID_PLATFORMS.inspect}"
267
+ end
268
+
269
+ # Save sources passed in a key
270
+ if opts.has_key?("source")
271
+ source = normalize_source(opts["source"])
272
+ opts["source"] = @sources.add_rubygems_source("remotes" => source)
273
+ end
274
+
275
+ git_name = (git_names & opts.keys).last
276
+ if @git_sources[git_name]
277
+ opts["git"] = @git_sources[git_name].call(opts[git_name])
278
+ end
279
+
280
+ ["git", "path"].each do |type|
281
+ if param = opts[type]
282
+ if version.first && version.first =~ /^\s*=?\s*(\d[^\s]*)\s*$/
283
+ options = opts.merge("name" => name, "version" => $1)
284
+ else
285
+ options = opts.dup
286
+ end
287
+ source = send(type, param, options) {}
288
+ opts["source"] = source
289
+ end
290
+ end
291
+
292
+ opts["source"] ||= @source
293
+ opts["env"] ||= @env
294
+ opts["platforms"] = platforms.dup
295
+ opts["group"] = groups
296
+ end
297
+
298
+ def normalize_source(source)
299
+ case source
300
+ when :gemcutter, :rubygems, :rubyforge
301
+ Carat.ui.warn "The source :#{source} is deprecated because HTTP " \
302
+ "requests are insecure.\nPlease change your source to 'https://" \
303
+ "rubygems.org' if possible, or 'http://rubygems.org' if not."
304
+ "http://rubygems.org"
305
+ when String
306
+ source
307
+ else
308
+ raise GemfileError, "Unknown source '#{source}'"
309
+ end
310
+ end
311
+
312
+ def check_primary_source_safety(source)
313
+ return unless source.rubygems_primary_remotes.any?
314
+
315
+ if Carat.settings[:disable_multisource]
316
+ raise GemspecError, "Warning: this Gemfile contains multiple primary sources. " \
317
+ "Each source after the first must include a block to indicate which gems " \
318
+ "should come from that source. To downgrade this error to a warning, run " \
319
+ "`carat config --delete disable_multisource`."
320
+ else
321
+ Carat.ui.warn "Warning: this Gemfile contains multiple primary sources. " \
322
+ "Using `source` more than once without a block is a security risk, and " \
323
+ "may result in installing unexpected gems. To resolve this warning, use " \
324
+ "a block to indicate which gems should come from the secondary source. " \
325
+ "To upgrade this warning to an error, run `carat config " \
326
+ "disable_multisource true`."
327
+ end
328
+ end
329
+
330
+ end
331
+ end