berkshelf 5.5.0 → 5.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -3
  3. data/Gemfile +31 -31
  4. data/Gemfile.lock +3 -3
  5. data/Guardfile +13 -13
  6. data/Rakefile +1 -0
  7. data/Thorfile +16 -16
  8. data/berkshelf.gemspec +35 -35
  9. data/features/step_definitions/chef/config_steps.rb +4 -4
  10. data/features/step_definitions/chef_server_steps.rb +6 -6
  11. data/features/step_definitions/cli_steps.rb +3 -3
  12. data/features/step_definitions/config_steps.rb +5 -5
  13. data/features/step_definitions/filesystem_steps.rb +12 -11
  14. data/features/support/env.rb +21 -21
  15. data/lib/berkshelf.rb +66 -66
  16. data/lib/berkshelf/base_generator.rb +10 -11
  17. data/lib/berkshelf/berksfile.rb +38 -38
  18. data/lib/berkshelf/cached_cookbook.rb +7 -7
  19. data/lib/berkshelf/cli.rb +126 -126
  20. data/lib/berkshelf/commands/shelf.rb +19 -18
  21. data/lib/berkshelf/commands/test_command.rb +2 -2
  22. data/lib/berkshelf/community_rest.rb +38 -38
  23. data/lib/berkshelf/config.rb +42 -41
  24. data/lib/berkshelf/cookbook_generator.rb +38 -38
  25. data/lib/berkshelf/cookbook_store.rb +4 -4
  26. data/lib/berkshelf/core_ext/file_utils.rb +1 -1
  27. data/lib/berkshelf/dependency.rb +23 -21
  28. data/lib/berkshelf/downloader.rb +24 -25
  29. data/lib/berkshelf/errors.rb +17 -17
  30. data/lib/berkshelf/file_syncer.rb +9 -8
  31. data/lib/berkshelf/formatters/human.rb +3 -3
  32. data/lib/berkshelf/formatters/json.rb +2 -2
  33. data/lib/berkshelf/init_generator.rb +64 -64
  34. data/lib/berkshelf/installer.rb +103 -102
  35. data/lib/berkshelf/location.rb +9 -9
  36. data/lib/berkshelf/locations/git.rb +16 -16
  37. data/lib/berkshelf/locations/github.rb +1 -1
  38. data/lib/berkshelf/locations/path.rb +2 -2
  39. data/lib/berkshelf/lockfile.rb +309 -315
  40. data/lib/berkshelf/mixin/git.rb +3 -3
  41. data/lib/berkshelf/packager.rb +4 -4
  42. data/lib/berkshelf/resolver.rb +2 -2
  43. data/lib/berkshelf/resolver/graph.rb +1 -1
  44. data/lib/berkshelf/shell.rb +1 -1
  45. data/lib/berkshelf/source.rb +6 -6
  46. data/lib/berkshelf/source_uri.rb +2 -2
  47. data/lib/berkshelf/ssl_policies.rb +3 -3
  48. data/lib/berkshelf/thor.rb +1 -1
  49. data/lib/berkshelf/uploader.rb +48 -48
  50. data/lib/berkshelf/validator.rb +2 -2
  51. data/lib/berkshelf/version.rb +1 -1
  52. data/lib/berkshelf/visualizer.rb +11 -11
  53. data/spec/config/knife.rb +2 -2
  54. data/spec/fixtures/Berksfile +3 -3
  55. data/spec/fixtures/cookbook-path/jenkins-config/metadata.rb +3 -3
  56. data/spec/fixtures/cookbook-store/jenkins-2.0.3/metadata.rb +5 -5
  57. data/spec/fixtures/cookbook-store/jenkins-2.0.4/metadata.rb +4 -4
  58. data/spec/fixtures/cookbooks/example_cookbook-0.5.0/metadata.rb +3 -3
  59. data/spec/fixtures/cookbooks/example_cookbook/metadata.rb +3 -3
  60. data/spec/spec_helper.rb +9 -9
  61. data/spec/support/chef_api.rb +11 -12
  62. data/spec/support/chef_server.rb +10 -10
  63. data/spec/support/git.rb +23 -23
  64. data/spec/support/kitchen.rb +2 -2
  65. data/spec/support/matchers/filepath_matchers.rb +2 -2
  66. data/spec/support/path_helpers.rb +12 -12
  67. data/spec/support/shared_examples/formatter.rb +1 -1
  68. data/spec/unit/berkshelf/berksfile_spec.rb +78 -78
  69. data/spec/unit/berkshelf/cached_cookbook_spec.rb +42 -42
  70. data/spec/unit/berkshelf/cli_spec.rb +6 -6
  71. data/spec/unit/berkshelf/community_rest_spec.rb +83 -83
  72. data/spec/unit/berkshelf/config_spec.rb +13 -13
  73. data/spec/unit/berkshelf/cookbook_generator_spec.rb +39 -39
  74. data/spec/unit/berkshelf/cookbook_store_spec.rb +41 -41
  75. data/spec/unit/berkshelf/core_ext/file_utils_spec.rb +5 -6
  76. data/spec/unit/berkshelf/core_ext/pathname_spec.rb +1 -1
  77. data/spec/unit/berkshelf/dependency_spec.rb +43 -43
  78. data/spec/unit/berkshelf/downloader_spec.rb +20 -20
  79. data/spec/unit/berkshelf/errors_spec.rb +3 -3
  80. data/spec/unit/berkshelf/file_syncer_spec.rb +86 -86
  81. data/spec/unit/berkshelf/formatters/base_spec.rb +23 -23
  82. data/spec/unit/berkshelf/formatters/human_spec.rb +2 -2
  83. data/spec/unit/berkshelf/formatters/json_spec.rb +2 -2
  84. data/spec/unit/berkshelf/formatters/null_spec.rb +3 -3
  85. data/spec/unit/berkshelf/init_generator_spec.rb +92 -92
  86. data/spec/unit/berkshelf/installer_spec.rb +8 -8
  87. data/spec/unit/berkshelf/location_spec.rb +11 -11
  88. data/spec/unit/berkshelf/locations/base_spec.rb +35 -35
  89. data/spec/unit/berkshelf/locations/git_spec.rb +87 -87
  90. data/spec/unit/berkshelf/locations/path_spec.rb +40 -40
  91. data/spec/unit/berkshelf/lockfile_parser_spec.rb +71 -71
  92. data/spec/unit/berkshelf/lockfile_spec.rb +197 -197
  93. data/spec/unit/berkshelf/logger_spec.rb +3 -3
  94. data/spec/unit/berkshelf/mixin/logging_spec.rb +5 -5
  95. data/spec/unit/berkshelf/packager_spec.rb +2 -2
  96. data/spec/unit/berkshelf/resolver/graph_spec.rb +1 -1
  97. data/spec/unit/berkshelf/resolver_spec.rb +17 -17
  98. data/spec/unit/berkshelf/shell_spec.rb +34 -34
  99. data/spec/unit/berkshelf/source_spec.rb +12 -11
  100. data/spec/unit/berkshelf/source_uri_spec.rb +1 -1
  101. data/spec/unit/berkshelf/ssl_policies_spec.rb +25 -25
  102. data/spec/unit/berkshelf/uploader_spec.rb +54 -54
  103. data/spec/unit/berkshelf/validator_spec.rb +16 -16
  104. data/spec/unit/berkshelf/visualizer_spec.rb +17 -17
  105. data/spec/unit/berkshelf_spec.rb +18 -18
  106. metadata +5 -5
@@ -1,4 +1,4 @@
1
- require 'berkshelf/api-client'
1
+ require "berkshelf/api-client"
2
2
 
3
3
  module Berkshelf
4
4
  class Installer
@@ -31,13 +31,14 @@ module Berkshelf
31
31
  def run
32
32
  lockfile.reduce!
33
33
 
34
- Berkshelf.formatter.msg('Resolving cookbook dependencies...')
34
+ Berkshelf.formatter.msg("Resolving cookbook dependencies...")
35
35
 
36
- dependencies, cookbooks = if lockfile.trusted?
37
- install_from_lockfile
38
- else
39
- install_from_universe
40
- end
36
+ dependencies, cookbooks =
37
+ if lockfile.trusted?
38
+ install_from_lockfile
39
+ else
40
+ install_from_universe
41
+ end
41
42
 
42
43
  Berkshelf.log.debug " Finished resolving, calculating locks"
43
44
 
@@ -59,131 +60,131 @@ module Berkshelf
59
60
 
60
61
  private
61
62
 
62
- attr_reader :worker
63
+ attr_reader :worker
63
64
 
64
- class Worker
65
- include Celluloid
65
+ class Worker
66
+ include Celluloid
66
67
 
67
- attr_reader :berksfile
68
- attr_reader :downloader
68
+ attr_reader :berksfile
69
+ attr_reader :downloader
69
70
 
70
- def initialize(berksfile)
71
- @berksfile = berksfile
72
- @downloader = Downloader.new(berksfile)
73
- end
71
+ def initialize(berksfile)
72
+ @berksfile = berksfile
73
+ @downloader = Downloader.new(berksfile)
74
+ end
74
75
 
75
- # Install a specific dependency.
76
- #
77
- # @param [Dependency]
78
- # the dependency to install
79
- # @return [CachedCookbook]
80
- # the installed cookbook
81
- def install(dependency)
82
- Berkshelf.log.info "Installing #{dependency}"
76
+ # Install a specific dependency.
77
+ #
78
+ # @param [Dependency]
79
+ # the dependency to install
80
+ # @return [CachedCookbook]
81
+ # the installed cookbook
82
+ def install(dependency)
83
+ Berkshelf.log.info "Installing #{dependency}"
83
84
 
84
- if dependency.installed?
85
- Berkshelf.log.debug " Already installed - skipping install"
85
+ if dependency.installed?
86
+ Berkshelf.log.debug " Already installed - skipping install"
86
87
 
87
- Berkshelf.formatter.use(dependency)
88
- dependency.cached_cookbook
89
- else
90
- name, version = dependency.name, dependency.locked_version.to_s
91
- source = berksfile.source_for(name, version)
88
+ Berkshelf.formatter.use(dependency)
89
+ dependency.cached_cookbook
90
+ else
91
+ name, version = dependency.name, dependency.locked_version.to_s
92
+ source = berksfile.source_for(name, version)
92
93
 
93
- # Raise error if our Berksfile.lock has cookbook versions that
94
- # can't be found in sources
95
- raise MissingLockfileCookbookVersion.new(name, version, 'in any of the sources') unless source
94
+ # Raise error if our Berksfile.lock has cookbook versions that
95
+ # can't be found in sources
96
+ raise MissingLockfileCookbookVersion.new(name, version, "in any of the sources") unless source
96
97
 
97
- Berkshelf.log.debug " Downloading #{dependency.name} (#{dependency.locked_version}) from #{source}"
98
+ Berkshelf.log.debug " Downloading #{dependency.name} (#{dependency.locked_version}) from #{source}"
98
99
 
99
- cookbook = source.cookbook(name, version)
100
+ cookbook = source.cookbook(name, version)
100
101
 
101
- Berkshelf.log.debug " => #{cookbook.inspect}"
102
+ Berkshelf.log.debug " => #{cookbook.inspect}"
102
103
 
103
- Berkshelf.formatter.install(source, cookbook)
104
+ Berkshelf.formatter.install(source, cookbook)
104
105
 
105
- downloader.download(name, version) do |stash|
106
- CookbookStore.import(name, version, stash)
107
- end
106
+ downloader.download(name, version) do |stash|
107
+ CookbookStore.import(name, version, stash)
108
108
  end
109
109
  end
110
110
  end
111
+ end
111
112
 
112
- # Install all the dependencies from the lockfile graph.
113
- #
114
- # @return [Array<Array<Dependency> Array<CachedCookbook>>]
115
- # the list of installed dependencies and cookbooks
116
- def install_from_lockfile
117
- Berkshelf.log.info "Installing from lockfile"
118
-
119
- dependencies = lockfile.graph.locks.values
120
-
121
- Berkshelf.log.debug " Dependencies"
122
- dependencies.map do |dependency|
123
- Berkshelf.log.debug " #{dependency}"
124
- end
125
-
126
- download_locations(dependencies)
127
-
128
- # Only construct the universe if we are going to install things
129
- unless dependencies.all?(&:installed?)
130
- Berkshelf.log.debug " Not all dependencies are installed"
131
- build_universe
132
- end
113
+ # Install all the dependencies from the lockfile graph.
114
+ #
115
+ # @return [Array<Array<Dependency> Array<CachedCookbook>>]
116
+ # the list of installed dependencies and cookbooks
117
+ def install_from_lockfile
118
+ Berkshelf.log.info "Installing from lockfile"
133
119
 
134
- cookbooks = dependencies.sort.map { |dependency| worker.future.install(dependency) }.map(&:value)
120
+ dependencies = lockfile.graph.locks.values
135
121
 
136
- [dependencies, cookbooks]
122
+ Berkshelf.log.debug " Dependencies"
123
+ dependencies.map do |dependency|
124
+ Berkshelf.log.debug " #{dependency}"
137
125
  end
138
126
 
139
- # Resolve and install the dependencies from the "universe", updating the
140
- # lockfile appropiately.
141
- #
142
- # @return [Array<Array<Dependency> Array<CachedCookbook>>]
143
- # the list of installed dependencies and cookbooks
144
- def install_from_universe
145
- Berkshelf.log.info "Installing from universe"
146
-
147
- dependencies = lockfile.graph.locks.values + berksfile.dependencies
148
- dependencies = dependencies.inject({}) do |hash, dependency|
149
- # Fancy way of ensuring no duplicate dependencies are used...
150
- hash[dependency.name] ||= dependency
151
- hash
152
- end.values
127
+ download_locations(dependencies)
153
128
 
154
- download_locations(dependencies)
129
+ # Only construct the universe if we are going to install things
130
+ unless dependencies.all?(&:installed?)
131
+ Berkshelf.log.debug " Not all dependencies are installed"
132
+ build_universe
133
+ end
155
134
 
156
- Berkshelf.log.debug " Creating a resolver"
157
- resolver = Resolver.new(berksfile, dependencies)
135
+ cookbooks = dependencies.sort.map { |dependency| worker.future.install(dependency) }.map(&:value)
158
136
 
159
- # Unlike when installing from the lockfile, we _always_ need to build
160
- # the universe when installing from the universe... duh
161
- build_universe
137
+ [dependencies, cookbooks]
138
+ end
162
139
 
163
- # Add any explicit dependencies for already-downloaded cookbooks (like
164
- # path locations)
165
- dependencies.each do |dependency|
166
- if dependency.location
167
- cookbook = dependency.cached_cookbook
168
- Berkshelf.log.debug " Adding explicit dependency on #{cookbook}"
169
- resolver.add_explicit_dependencies(cookbook)
170
- end
140
+ # Resolve and install the dependencies from the "universe", updating the
141
+ # lockfile appropiately.
142
+ #
143
+ # @return [Array<Array<Dependency> Array<CachedCookbook>>]
144
+ # the list of installed dependencies and cookbooks
145
+ def install_from_universe
146
+ Berkshelf.log.info "Installing from universe"
147
+
148
+ dependencies = lockfile.graph.locks.values + berksfile.dependencies
149
+ dependencies = dependencies.inject({}) do |hash, dependency|
150
+ # Fancy way of ensuring no duplicate dependencies are used...
151
+ hash[dependency.name] ||= dependency
152
+ hash
153
+ end.values
154
+
155
+ download_locations(dependencies)
156
+
157
+ Berkshelf.log.debug " Creating a resolver"
158
+ resolver = Resolver.new(berksfile, dependencies)
159
+
160
+ # Unlike when installing from the lockfile, we _always_ need to build
161
+ # the universe when installing from the universe... duh
162
+ build_universe
163
+
164
+ # Add any explicit dependencies for already-downloaded cookbooks (like
165
+ # path locations)
166
+ dependencies.each do |dependency|
167
+ if dependency.location
168
+ cookbook = dependency.cached_cookbook
169
+ Berkshelf.log.debug " Adding explicit dependency on #{cookbook}"
170
+ resolver.add_explicit_dependencies(cookbook)
171
171
  end
172
+ end
172
173
 
173
- Berkshelf.log.debug " Starting resolution..."
174
+ Berkshelf.log.debug " Starting resolution..."
174
175
 
175
- cookbooks = resolver.resolve.sort.map { |dependency| worker.future.install(dependency) }.map(&:value)
176
+ cookbooks = resolver.resolve.sort.map { |dependency| worker.future.install(dependency) }.map(&:value)
176
177
 
177
- [dependencies, cookbooks]
178
- end
178
+ [dependencies, cookbooks]
179
+ end
179
180
 
180
- def download_locations(dependencies)
181
- dependencies.select(&:location).each do |dependency|
182
- unless dependency.location.installed?
183
- Berkshelf.formatter.fetch(dependency)
184
- dependency.location.install
185
- end
181
+ def download_locations(dependencies)
182
+ dependencies.select(&:location).each do |dependency|
183
+ unless dependency.location.installed?
184
+ Berkshelf.formatter.fetch(dependency)
185
+ dependency.location.install
186
186
  end
187
187
  end
188
+ end
188
189
  end
189
190
  end
@@ -31,17 +31,17 @@ module Berkshelf
31
31
  # Load the correct location from the given options.
32
32
  #
33
33
  # @return [Class, nil]
34
- def klass_from_options(options)
35
- options.each do |key, _|
36
- id = key.to_s.capitalize
34
+ def klass_from_options(options)
35
+ options.each do |key, _|
36
+ id = key.to_s.capitalize
37
37
 
38
- begin
39
- return Berkshelf.const_get("#{id}Location")
40
- rescue NameError; end
41
- end
42
-
43
- nil
38
+ begin
39
+ return Berkshelf.const_get("#{id}Location")
40
+ rescue NameError; end
44
41
  end
42
+
43
+ nil
44
+ end
45
45
  end
46
46
  end
47
47
  end
@@ -1,4 +1,4 @@
1
- require 'buff/shell_out'
1
+ require "buff/shell_out"
2
2
 
3
3
  module Berkshelf
4
4
  class GitLocation < BaseLocation
@@ -22,7 +22,7 @@ module Berkshelf
22
22
  @rel = options[:rel]
23
23
 
24
24
  # The revision to parse
25
- @rev_parse = options[:ref] || options[:branch] || options[:tag] || 'master'
25
+ @rev_parse = options[:ref] || options[:branch] || options[:tag] || "master"
26
26
  end
27
27
 
28
28
  # @see BaseLoation#installed?
@@ -40,26 +40,26 @@ module Berkshelf
40
40
 
41
41
  if cached?
42
42
  Dir.chdir(cache_path) do
43
- git %|fetch --force --tags #{uri} "refs/heads/*:refs/heads/*"|
43
+ git %{fetch --force --tags #{uri} "refs/heads/*:refs/heads/*"}
44
44
  end
45
45
  else
46
- git %|clone #{uri} "#{cache_path}" --bare --no-hardlinks|
46
+ git %{clone #{uri} "#{cache_path}" --bare --no-hardlinks}
47
47
  end
48
48
 
49
49
  Dir.chdir(cache_path) do
50
- @revision ||= git %|rev-parse #{@rev_parse}|
50
+ @revision ||= git %{rev-parse #{@rev_parse}}
51
51
  end
52
52
 
53
53
  # Clone into a scratch directory for validations
54
- git %|clone --no-checkout "#{cache_path}" "#{scratch_path}"|
54
+ git %{clone --no-checkout "#{cache_path}" "#{scratch_path}"}
55
55
 
56
56
  # Make sure the scratch directory is up-to-date and account for rel paths
57
57
  Dir.chdir(scratch_path) do
58
- git %|fetch --force --tags "#{cache_path}"|
59
- git %|reset --hard #{@revision}|
58
+ git %{fetch --force --tags "#{cache_path}"}
59
+ git %{reset --hard #{@revision}}
60
60
 
61
61
  if rel
62
- git %|filter-branch --subdirectory-filter "#{rel}" --force|
62
+ git %{filter-branch --subdirectory-filter "#{rel}" --force}
63
63
  end
64
64
  end
65
65
 
@@ -71,7 +71,7 @@ module Berkshelf
71
71
  FileUtils.mv(scratch_path, install_path)
72
72
 
73
73
  # Remove the git history
74
- FileUtils.rm_rf(File.join(install_path, '.git'))
74
+ FileUtils.rm_rf(File.join(install_path, ".git"))
75
75
 
76
76
  install_path.chmod(0777 & ~File.umask)
77
77
  ensure
@@ -90,11 +90,11 @@ module Berkshelf
90
90
 
91
91
  def ==(other)
92
92
  other.is_a?(GitLocation) &&
93
- other.uri == uri &&
94
- other.branch == branch &&
95
- other.tag == tag &&
96
- other.shortref == shortref &&
97
- other.rel == rel
93
+ other.uri == uri &&
94
+ other.branch == branch &&
95
+ other.tag == tag &&
96
+ other.shortref == shortref &&
97
+ other.rel == rel
98
98
  end
99
99
 
100
100
  def to_s
@@ -149,7 +149,7 @@ module Berkshelf
149
149
  # @return [Pathname]
150
150
  def cache_path
151
151
  Pathname.new(Berkshelf.berkshelf_path)
152
- .join('.cache', 'git', Digest::SHA1.hexdigest(uri))
152
+ .join(".cache", "git", Digest::SHA1.hexdigest(uri))
153
153
  end
154
154
  end
155
155
  end
@@ -1,6 +1,6 @@
1
1
  module Berkshelf
2
2
  class GithubLocation < GitLocation
3
- HOST = 'github.com'
3
+ HOST = "github.com"
4
4
  def initialize(dependency, options = {})
5
5
  protocol = Berkshelf::Config.instance.github_protocol || :https
6
6
  case protocol
@@ -49,8 +49,8 @@ module Berkshelf
49
49
 
50
50
  def ==(other)
51
51
  other.is_a?(PathLocation) &&
52
- other.metadata? == metadata? &&
53
- other.relative_path == relative_path
52
+ other.metadata? == metadata? &&
53
+ other.relative_path == relative_path
54
54
  end
55
55
 
56
56
  def to_lock
@@ -1,4 +1,4 @@
1
- require_relative 'dependency'
1
+ require_relative "dependency"
2
2
 
3
3
  module Berkshelf
4
4
  class Lockfile
@@ -24,10 +24,10 @@ module Berkshelf
24
24
  end
25
25
  end
26
26
 
27
- DEFAULT_FILENAME = 'Berksfile.lock'.freeze
27
+ DEFAULT_FILENAME = "Berksfile.lock".freeze
28
28
 
29
- DEPENDENCIES = 'DEPENDENCIES'.freeze
30
- GRAPH = 'GRAPH'.freeze
29
+ DEPENDENCIES = "DEPENDENCIES".freeze
30
+ GRAPH = "GRAPH".freeze
31
31
 
32
32
  include Berkshelf::Mixin::Logging
33
33
 
@@ -94,7 +94,7 @@ module Berkshelf
94
94
  # @return [Boolean]
95
95
  # true if this lockfile is trusted, false otherwise
96
96
  def trusted?
97
- Berkshelf.log.info 'Checking if lockfile is trusted'
97
+ Berkshelf.log.info "Checking if lockfile is trusted"
98
98
 
99
99
  checked = {}
100
100
 
@@ -154,7 +154,7 @@ module Berkshelf
154
154
  #
155
155
  # @return [Boolean]
156
156
  def satisfies_transitive?(graph_item, checked, level = 0)
157
- indent = ' '*(level + 2)
157
+ indent = " " * (level + 2)
158
158
 
159
159
  Berkshelf.log.debug "#{indent}Checking transitive dependencies for #{graph_item}"
160
160
 
@@ -204,8 +204,8 @@ module Berkshelf
204
204
  # configuration
205
205
  def apply(name, options = {})
206
206
  locks = graph.locks.inject({}) do |hash, (name, dependency)|
207
- hash[name] = "= #{dependency.locked_version.to_s}"
208
- hash
207
+ hash[name] = "= #{dependency.locked_version}"
208
+ hash
209
209
  end
210
210
 
211
211
  if options[:envfile]
@@ -297,7 +297,7 @@ module Berkshelf
297
297
  unless locked.installed?
298
298
  name = locked.name
299
299
  version = locked.locked_version || locked.version_constraint
300
- raise CookbookNotFound.new(name, version, 'in the cookbook store')
300
+ raise CookbookNotFound.new(name, version, "in the cookbook store")
301
301
  end
302
302
 
303
303
  locked.cached_cookbook
@@ -320,11 +320,11 @@ module Berkshelf
320
320
 
321
321
  json_environment = JSON.parse(File.read(environment_file))
322
322
 
323
- json_environment['cookbook_versions'] = locks
323
+ json_environment["cookbook_versions"] = locks
324
324
 
325
325
  json = JSON.pretty_generate(json_environment)
326
326
 
327
- File.open(environment_file, 'w'){ |f| f.puts(json) }
327
+ File.open(environment_file, "w") { |f| f.puts(json) }
328
328
 
329
329
  Berkshelf.log.info "Updated environment file #{environment_file}"
330
330
  end
@@ -395,7 +395,6 @@ module Berkshelf
395
395
  end
396
396
  Berkshelf.log.debug ""
397
397
 
398
-
399
398
  # Unlock any locked dependencies that are no longer in the Berksfile
400
399
  Berkshelf.log.debug "Unlocking dependencies no longer in the Berksfile"
401
400
 
@@ -441,11 +440,11 @@ module Berkshelf
441
440
 
442
441
  # Iteratively remove orphan dependencies
443
442
  orphans = true
444
- while orphans do
443
+ while orphans
445
444
  orphans = false
446
445
  graph.each do |cookbook|
447
446
  name = cookbook.name
448
- unless dependency?(name) or graph.dependency?(name)
447
+ unless dependency?(name) || graph.dependency?(name)
449
448
  Berkshelf.log.debug "#{cookbook} identified as orphan; removing it"
450
449
  unlock(name)
451
450
  orphans = true
@@ -461,7 +460,6 @@ module Berkshelf
461
460
  Berkshelf.log.debug ""
462
461
  end
463
462
 
464
-
465
463
  # Write the contents of the current statue of the lockfile to disk. This
466
464
  # method uses an atomic file write. A temporary file is created, written,
467
465
  # and then copied over the existing one. This ensures any partial updates
@@ -473,7 +471,7 @@ module Berkshelf
473
471
  def save
474
472
  return false if dependencies.empty?
475
473
 
476
- tempfile = Tempfile.new(['Berksfile', '.lock'])
474
+ tempfile = Tempfile.new(["Berksfile", ".lock"])
477
475
 
478
476
  tempfile.write(to_lock)
479
477
 
@@ -509,359 +507,355 @@ module Berkshelf
509
507
  "#<Berkshelf::Lockfile #{Pathname.new(filepath).basename}, dependencies: #{dependencies.inspect}>"
510
508
  end
511
509
 
512
- private
513
-
514
510
  # The class responsible for parsing the lockfile and turning it into a
515
511
  # useful data structure.
516
- class LockfileParser
517
- NAME_VERSION = '(?! )(.*?)(?: \(([^-]*)(?:-(.*))?\))?'.freeze
518
- DEPENDENCY_PATTERN = /^ {2}#{NAME_VERSION}$/.freeze
519
- DEPENDENCIES_PATTERN = /^ {4}#{NAME_VERSION}$/.freeze
520
- OPTION_PATTERN = /^ {4}(.+)\: (.+)/.freeze
512
+ class LockfileParser
513
+ NAME_VERSION = '(?! )(.*?)(?: \(([^-]*)(?:-(.*))?\))?'.freeze
514
+ DEPENDENCY_PATTERN = /^ {2}#{NAME_VERSION}$/
515
+ DEPENDENCIES_PATTERN = /^ {4}#{NAME_VERSION}$/
516
+ OPTION_PATTERN = /^ {4}(.+)\: (.+)/
521
517
 
522
- # Create a new lockfile parser.
523
- #
524
- # @param [Lockfile]
525
- def initialize(lockfile)
526
- @lockfile = lockfile
527
- @berksfile = lockfile.berksfile
528
- end
518
+ # Create a new lockfile parser.
519
+ #
520
+ # @param [Lockfile]
521
+ def initialize(lockfile)
522
+ @lockfile = lockfile
523
+ @berksfile = lockfile.berksfile
524
+ end
529
525
 
530
- # Parse the lockfile contents, adding the correct things to the lockfile.
531
- #
532
- # @return [true]
533
- def run
534
- @parsed_dependencies = {}
526
+ # Parse the lockfile contents, adding the correct things to the lockfile.
527
+ #
528
+ # @return [true]
529
+ def run
530
+ @parsed_dependencies = {}
535
531
 
536
- contents = File.read(@lockfile.filepath)
532
+ contents = File.read(@lockfile.filepath)
537
533
 
538
- if contents.strip.empty?
539
- Berkshelf.formatter.warn "Your lockfile at '#{@lockfile.filepath}' " \
540
- "is empty. I am going to parse it anyway, but there is a chance " \
541
- "that a larger problem is at play. If you manually edited your " \
542
- "lockfile, you may have corrupted it."
543
- end
534
+ if contents.strip.empty?
535
+ Berkshelf.formatter.warn "Your lockfile at '#{@lockfile.filepath}' " \
536
+ "is empty. I am going to parse it anyway, but there is a chance " \
537
+ "that a larger problem is at play. If you manually edited your " \
538
+ "lockfile, you may have corrupted it."
539
+ end
544
540
 
545
- if contents.strip[0] == '{'
546
- Berkshelf.formatter.warn "It looks like you are using an older " \
547
- "version of the lockfile. Attempting to convert..."
548
-
549
- dependencies = "#{Lockfile::DEPENDENCIES}\n"
550
- graph = "#{Lockfile::GRAPH}\n"
551
-
552
- begin
553
- hash = JSON.parse(contents)
554
- rescue JSON::ParserError
555
- Berkshelf.formatter.warn "Could not convert lockfile! This is a " \
556
- "problem. You see, previous versions of the lockfile were " \
557
- "actually a lie. It lied to you about your version locks, and we " \
558
- "are really sorry about that.\n\n" \
559
- "Here's the good news - we fixed it!\n\n" \
560
- "Here's the bad news - you probably should not trust your old " \
561
- "lockfile. You should manually delete your old lockfile and " \
562
- "re-run the installer."
563
- end
541
+ if contents.strip[0] == "{"
542
+ Berkshelf.formatter.warn "It looks like you are using an older " \
543
+ "version of the lockfile. Attempting to convert..."
544
+
545
+ dependencies = "#{Lockfile::DEPENDENCIES}\n"
546
+ graph = "#{Lockfile::GRAPH}\n"
547
+
548
+ begin
549
+ hash = JSON.parse(contents)
550
+ rescue JSON::ParserError
551
+ Berkshelf.formatter.warn "Could not convert lockfile! This is a " \
552
+ "problem. You see, previous versions of the lockfile were " \
553
+ "actually a lie. It lied to you about your version locks, and we " \
554
+ "are really sorry about that.\n\n" \
555
+ "Here's the good news - we fixed it!\n\n" \
556
+ "Here's the bad news - you probably should not trust your old " \
557
+ "lockfile. You should manually delete your old lockfile and " \
558
+ "re-run the installer."
559
+ end
564
560
 
565
- hash['dependencies'] && hash['dependencies'].sort .each do |name, info|
566
- dependencies << " #{name} (>= 0.0.0)\n"
567
- info.each do |key, value|
568
- unless key == 'locked_version'
569
- dependencies << " #{key}: #{value}\n"
570
- end
561
+ hash["dependencies"] && hash["dependencies"].sort .each do |name, info|
562
+ dependencies << " #{name} (>= 0.0.0)\n"
563
+ info.each do |key, value|
564
+ unless key == "locked_version"
565
+ dependencies << " #{key}: #{value}\n"
571
566
  end
572
-
573
- graph << " #{name} (#{info['locked_version']})\n"
574
567
  end
575
568
 
576
- contents = "#{dependencies}\n#{graph}"
569
+ graph << " #{name} (#{info['locked_version']})\n"
577
570
  end
578
571
 
579
- contents.split(/(?:\r?\n)+/).each do |line|
580
- if line == Lockfile::DEPENDENCIES
581
- @state = :dependency
582
- elsif line == Lockfile::GRAPH
583
- @state = :graph
584
- else
585
- send("parse_#{@state}", line)
586
- end
587
- end
588
-
589
- @parsed_dependencies.each do |name, options|
590
- graph_item = @lockfile.graph.find(name)
591
- options[:locked_version] = graph_item.version if graph_item
572
+ contents = "#{dependencies}\n#{graph}"
573
+ end
592
574
 
593
- dependency = Dependency.new(@berksfile, name, options)
594
- @lockfile.add(dependency)
575
+ contents.split(/(?:\r?\n)+/).each do |line|
576
+ if line == Lockfile::DEPENDENCIES
577
+ @state = :dependency
578
+ elsif line == Lockfile::GRAPH
579
+ @state = :graph
580
+ else
581
+ send("parse_#{@state}", line)
595
582
  end
596
-
597
- true
598
583
  end
599
584
 
600
- private
601
-
602
- # Parse a dependency line.
603
- #
604
- # @param [String] line
605
- def parse_dependency(line)
606
- if line =~ DEPENDENCY_PATTERN
607
- name, version = $1, $2
608
-
609
- @parsed_dependencies[name] ||= {}
610
- @parsed_dependencies[name][:constraint] = version if version
611
- @current_dependency = @parsed_dependencies[name]
612
- elsif line =~ OPTION_PATTERN
613
- key, value = $1, $2
614
- @current_dependency[key.to_sym] = value
615
- end
616
- end
585
+ @parsed_dependencies.each do |name, options|
586
+ graph_item = @lockfile.graph.find(name)
587
+ options[:locked_version] = graph_item.version if graph_item
617
588
 
618
- # Parse a graph line.
619
- #
620
- # @param [String] line
621
- def parse_graph(line)
622
- if line =~ DEPENDENCY_PATTERN
623
- name, version = $1, $2
624
-
625
- @lockfile.graph.find(name) || @lockfile.graph.add(name, version)
626
- @current_lock = name
627
- elsif line =~ DEPENDENCIES_PATTERN
628
- name, constraint = $1, $2
629
- @lockfile.graph.find(@current_lock).add_dependency(name, constraint)
630
- end
631
- end
589
+ dependency = Dependency.new(@berksfile, name, options)
590
+ @lockfile.add(dependency)
591
+ end
592
+
593
+ true
632
594
  end
633
595
 
634
- # The class representing an internal graph.
635
- class Graph
636
- include Enumerable
596
+ private
637
597
 
638
- # Create a new Lockfile graph.
598
+ # Parse a dependency line.
639
599
  #
640
- # Some clarifying terminology:
641
- #
642
- # yum-epel (0.2.0) <- lock
643
- # yum (~> 3.0) <- dependency
644
- #
645
- # @return [Graph]
646
- def initialize(lockfile)
647
- @lockfile = lockfile
648
- @berksfile = lockfile.berksfile
649
- @graph = {}
650
- end
651
-
652
- # @yield [Hash<String]
653
- def each(&block)
654
- @graph.values.each(&block)
600
+ # @param [String] line
601
+ def parse_dependency(line)
602
+ if line =~ DEPENDENCY_PATTERN
603
+ name, version = $1, $2
604
+
605
+ @parsed_dependencies[name] ||= {}
606
+ @parsed_dependencies[name][:constraint] = version if version
607
+ @current_dependency = @parsed_dependencies[name]
608
+ elsif line =~ OPTION_PATTERN
609
+ key, value = $1, $2
610
+ @current_dependency[key.to_sym] = value
655
611
  end
612
+ end
656
613
 
657
- # The list of locks for this graph. Dependencies are retrieved from the
658
- # lockfile, then the Berksfile, and finally a new dependency object is
659
- # created if none of those exist.
614
+ # Parse a graph line.
660
615
  #
661
- # @return [Hash<String, Dependency>]
662
- # a key-value hash where the key is the name of the cookbook and the
663
- # value is the locked dependency
664
- def locks
665
- @graph.sort.inject({}) do |hash, (name, item)|
666
- dependency = @lockfile.find(name) ||
667
- @berksfile && @berksfile.find(name) ||
668
- Dependency.new(@berksfile, name)
669
-
670
- # We need to make a copy of the dependency, or else we could be
671
- # modifying an existing object that other processes depend on!
672
- dependency = dependency.dup
673
- dependency.locked_version = item.version unless dependency.locked_version
674
-
675
- hash[item.name] = dependency
676
- hash
677
- end
616
+ # @param [String] line
617
+ def parse_graph(line)
618
+ if line =~ DEPENDENCY_PATTERN
619
+ name, version = $1, $2
620
+
621
+ @lockfile.graph.find(name) || @lockfile.graph.add(name, version)
622
+ @current_lock = name
623
+ elsif line =~ DEPENDENCIES_PATTERN
624
+ name, constraint = $1, $2
625
+ @lockfile.graph.find(@current_lock).add_dependency(name, constraint)
678
626
  end
627
+ end
628
+ end
679
629
 
680
- # Find a given dependency in the graph.
681
- #
682
- # @param [Dependency, String]
683
- # the name/dependency to find
684
- #
685
- # @return [GraphItem, nil]
686
- # the item for the name
687
- def find(dependency)
688
- @graph[Dependency.name(dependency)]
689
- end
630
+ # The class representing an internal graph.
631
+ class Graph
632
+ include Enumerable
690
633
 
691
- # Find if the given lock exists?
692
- #
693
- # @param [Dependency, String]
694
- # the name/dependency to find
695
- #
696
- # @return [true, false]
697
- def lock?(dependency)
698
- !find(dependency).nil?
634
+ # Create a new Lockfile graph.
635
+ #
636
+ # Some clarifying terminology:
637
+ #
638
+ # yum-epel (0.2.0) <- lock
639
+ # yum (~> 3.0) <- dependency
640
+ #
641
+ # @return [Graph]
642
+ def initialize(lockfile)
643
+ @lockfile = lockfile
644
+ @berksfile = lockfile.berksfile
645
+ @graph = {}
646
+ end
647
+
648
+ # @yield [Hash<String]
649
+ def each(&block)
650
+ @graph.values.each(&block)
651
+ end
652
+
653
+ # The list of locks for this graph. Dependencies are retrieved from the
654
+ # lockfile, then the Berksfile, and finally a new dependency object is
655
+ # created if none of those exist.
656
+ #
657
+ # @return [Hash<String, Dependency>]
658
+ # a key-value hash where the key is the name of the cookbook and the
659
+ # value is the locked dependency
660
+ def locks
661
+ @graph.sort.inject({}) do |hash, (name, item)|
662
+ dependency = @lockfile.find(name) ||
663
+ @berksfile && @berksfile.find(name) ||
664
+ Dependency.new(@berksfile, name)
665
+
666
+ # We need to make a copy of the dependency, or else we could be
667
+ # modifying an existing object that other processes depend on!
668
+ dependency = dependency.dup
669
+ dependency.locked_version = item.version unless dependency.locked_version
670
+
671
+ hash[item.name] = dependency
672
+ hash
699
673
  end
700
- alias_method :has_lock?, :lock?
674
+ end
701
675
 
702
- # Determine if this graph contains the given dependency. This method is
703
- # used by the lockfile when adding or removing dependencies to see if a
704
- # dependency can be safely removed.
705
- #
706
- # @param [Dependency, String] dependency
707
- # the name/dependency to find
708
- #
709
- # @option options [String, Array<String>] :ignore
710
- # the list of dependencies to ignore
711
- def dependency?(dependency, options = {})
712
- name = Dependency.name(dependency)
713
- ignore = Hash[*Array(options[:ignore]).map { |i| [i, true] }.flatten]
676
+ # Find a given dependency in the graph.
677
+ #
678
+ # @param [Dependency, String]
679
+ # the name/dependency to find
680
+ #
681
+ # @return [GraphItem, nil]
682
+ # the item for the name
683
+ def find(dependency)
684
+ @graph[Dependency.name(dependency)]
685
+ end
714
686
 
715
- @graph.values.each do |item|
716
- next if ignore[item.name]
687
+ # Find if the given lock exists?
688
+ #
689
+ # @param [Dependency, String]
690
+ # the name/dependency to find
691
+ #
692
+ # @return [true, false]
693
+ def lock?(dependency)
694
+ !find(dependency).nil?
695
+ end
696
+ alias_method :has_lock?, :lock?
717
697
 
718
- if item.dependencies.key?(name)
719
- return true
720
- end
721
- end
698
+ # Determine if this graph contains the given dependency. This method is
699
+ # used by the lockfile when adding or removing dependencies to see if a
700
+ # dependency can be safely removed.
701
+ #
702
+ # @param [Dependency, String] dependency
703
+ # the name/dependency to find
704
+ #
705
+ # @option options [String, Array<String>] :ignore
706
+ # the list of dependencies to ignore
707
+ def dependency?(dependency, options = {})
708
+ name = Dependency.name(dependency)
709
+ ignore = Hash[*Array(options[:ignore]).map { |i| [i, true] }.flatten]
722
710
 
723
- false
724
- end
725
- alias_method :has_dependency?, :dependency?
711
+ @graph.values.each do |item|
712
+ next if ignore[item.name]
726
713
 
727
- # Add each a new {GraphItem} to the graph.
728
- #
729
- # @param [#to_s] name
730
- # the name of the cookbook
731
- # @param [#to_s] version
732
- # the version of the lock
733
- #
734
- # @return [GraphItem]
735
- def add(name, version)
736
- @graph[name.to_s] = GraphItem.new(name, version)
714
+ if item.dependencies.key?(name)
715
+ return true
716
+ end
737
717
  end
738
718
 
739
- # Recursively remove any dependencies from the graph unless they exist as
740
- # top-level dependencies or nested dependencies.
741
- #
742
- # @param [Dependency, String] dependency
743
- # the name/dependency to remove
744
- #
745
- # @option options [String, Array<String>] :ignore
746
- # the list of dependencies to ignore
747
- def remove(dependency, options = {})
748
- name = Dependency.name(dependency)
719
+ false
720
+ end
721
+ alias_method :has_dependency?, :dependency?
749
722
 
750
- if @lockfile.dependency?(name)
751
- return
752
- end
723
+ # Add each a new {GraphItem} to the graph.
724
+ #
725
+ # @param [#to_s] name
726
+ # the name of the cookbook
727
+ # @param [#to_s] version
728
+ # the version of the lock
729
+ #
730
+ # @return [GraphItem]
731
+ def add(name, version)
732
+ @graph[name.to_s] = GraphItem.new(name, version)
733
+ end
753
734
 
754
- if dependency?(name, options)
755
- return
756
- end
735
+ # Recursively remove any dependencies from the graph unless they exist as
736
+ # top-level dependencies or nested dependencies.
737
+ #
738
+ # @param [Dependency, String] dependency
739
+ # the name/dependency to remove
740
+ #
741
+ # @option options [String, Array<String>] :ignore
742
+ # the list of dependencies to ignore
743
+ def remove(dependency, options = {})
744
+ name = Dependency.name(dependency)
745
+
746
+ if @lockfile.dependency?(name)
747
+ return
748
+ end
749
+
750
+ if dependency?(name, options)
751
+ return
752
+ end
753
+
754
+ # Grab the nested dependencies for this particular entry so we can
755
+ # recurse and try to remove them from the graph.
756
+ locked = @graph[name]
757
+ nested_dependencies = locked && locked.dependencies.keys || []
757
758
 
758
- # Grab the nested dependencies for this particular entry so we can
759
- # recurse and try to remove them from the graph.
760
- locked = @graph[name]
761
- nested_dependencies = locked && locked.dependencies.keys || []
759
+ # Now delete the entry
760
+ @graph.delete(name)
762
761
 
763
- # Now delete the entry
764
- @graph.delete(name)
762
+ # Recursively try to delete the remaining dependencies for this item
763
+ nested_dependencies.each(&method(:remove))
764
+ end
765
765
 
766
- # Recursively try to delete the remaining dependencies for this item
767
- nested_dependencies.each(&method(:remove))
766
+ # Update the graph with the given cookbooks. This method destroys the
767
+ # existing dependency graph with this new result!
768
+ #
769
+ # @param [Array<CachedCookbook>]
770
+ # the list of cookbooks to populate the graph with
771
+ def update(cookbooks)
772
+ @graph = {}
773
+
774
+ cookbooks.each do |cookbook|
775
+ @graph[cookbook.cookbook_name.to_s] = GraphItem.new(
776
+ cookbook.cookbook_name,
777
+ cookbook.version,
778
+ cookbook.dependencies
779
+ )
768
780
  end
781
+ end
769
782
 
770
- # Update the graph with the given cookbooks. This method destroys the
771
- # existing dependency graph with this new result!
772
- #
773
- # @param [Array<CachedCookbook>]
774
- # the list of cookbooks to populate the graph with
775
- def update(cookbooks)
776
- @graph = {}
777
-
778
- cookbooks.each do |cookbook|
779
- @graph[cookbook.cookbook_name.to_s] = GraphItem.new(
780
- cookbook.cookbook_name,
781
- cookbook.version,
782
- cookbook.dependencies,
783
- )
783
+ # Write the contents of the graph to the lockfile format.
784
+ #
785
+ # The resulting format looks like:
786
+ #
787
+ # GRAPH
788
+ # apache2 (1.8.14)
789
+ # yum-epel (0.2.0)
790
+ # yum (~> 3.0)
791
+ #
792
+ # @example lockfile.graph.to_lock #=> "GRAPH\n apache2 (1.18.14)\n..."
793
+ #
794
+ # @return [String]
795
+ #
796
+ def to_lock
797
+ out = "#{Lockfile::GRAPH}\n"
798
+ @graph.sort.each do |name, item|
799
+ out << " #{name} (#{item.version})\n"
800
+
801
+ unless item.dependencies.empty?
802
+ item.dependencies.sort.each do |name, constraint|
803
+ out << " #{name} (#{constraint})\n"
804
+ end
784
805
  end
785
806
  end
786
807
 
787
- # Write the contents of the graph to the lockfile format.
788
- #
789
- # The resulting format looks like:
790
- #
791
- # GRAPH
792
- # apache2 (1.8.14)
793
- # yum-epel (0.2.0)
794
- # yum (~> 3.0)
795
- #
796
- # @example lockfile.graph.to_lock #=> "GRAPH\n apache2 (1.18.14)\n..."
808
+ out
809
+ end
810
+
811
+ # A single item inside the graph.
812
+ class GraphItem
813
+ # The name of the cookbook that corresponds to this graph item.
797
814
  #
798
815
  # @return [String]
816
+ # the name of the cookbook
817
+ attr_reader :name
818
+
819
+ # The locked version for this graph item.
799
820
  #
800
- def to_lock
801
- out = "#{Lockfile::GRAPH}\n"
802
- @graph.sort.each do |name, item|
803
- out << " #{name} (#{item.version})\n"
804
-
805
- unless item.dependencies.empty?
806
- item.dependencies.sort.each do |name, constraint|
807
- out << " #{name} (#{constraint})\n"
808
- end
809
- end
810
- end
821
+ # @return [String]
822
+ # the locked version of the graph item (as a string)
823
+ attr_reader :version
811
824
 
812
- out
825
+ # The list of dependencies and their constraints.
826
+ #
827
+ # @return [Hash<String, String>]
828
+ # the list of dependencies for this graph item, where the key
829
+ # corresponds to the name of the dependency and the value is the
830
+ # version constraint.
831
+ attr_reader :dependencies
832
+
833
+ # Create a new graph item.
834
+ def initialize(name, version, dependencies = {})
835
+ @name = name.to_s
836
+ @version = version.to_s
837
+ @dependencies = dependencies
813
838
  end
814
839
 
815
- private
816
-
817
- # A single item inside the graph.
818
- class GraphItem
819
- # The name of the cookbook that corresponds to this graph item.
820
- #
821
- # @return [String]
822
- # the name of the cookbook
823
- attr_reader :name
824
-
825
- # The locked version for this graph item.
826
- #
827
- # @return [String]
828
- # the locked version of the graph item (as a string)
829
- attr_reader :version
830
-
831
- # The list of dependencies and their constraints.
832
- #
833
- # @return [Hash<String, String>]
834
- # the list of dependencies for this graph item, where the key
835
- # corresponds to the name of the dependency and the value is the
836
- # version constraint.
837
- attr_reader :dependencies
838
-
839
- # Create a new graph item.
840
- def initialize(name, version, dependencies = {})
841
- @name = name.to_s
842
- @version = version.to_s
843
- @dependencies = dependencies
844
- end
845
-
846
- # Add a new dependency to the list.
847
- #
848
- # @param [#to_s] name
849
- # the name to use
850
- # @param [#to_s] constraint
851
- # the version constraint to use
852
- def add_dependency(name, constraint)
853
- @dependencies[name.to_s] = constraint.to_s
854
- end
840
+ # Add a new dependency to the list.
841
+ #
842
+ # @param [#to_s] name
843
+ # the name to use
844
+ # @param [#to_s] constraint
845
+ # the version constraint to use
846
+ def add_dependency(name, constraint)
847
+ @dependencies[name.to_s] = constraint.to_s
848
+ end
855
849
 
856
- def set_dependencies(dependencies)
857
- @dependencies = dependencies.to_hash
858
- end
850
+ def set_dependencies(dependencies)
851
+ @dependencies = dependencies.to_hash
852
+ end
859
853
 
860
- # @private
861
- def to_s
862
- "#{name} (#{version})"
863
- end
864
- end
854
+ # @private
855
+ def to_s
856
+ "#{name} (#{version})"
857
+ end
865
858
  end
859
+ end
866
860
  end
867
861
  end