bibliothecary 7.3.6 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.circleci/config.yml +1 -1
  3. data/README.md +7 -0
  4. data/bibliothecary.gemspec +1 -0
  5. data/lib/bibliothecary/analyser/analysis.rb +110 -0
  6. data/lib/bibliothecary/analyser/determinations.rb +27 -0
  7. data/lib/bibliothecary/analyser/matchers.rb +64 -0
  8. data/lib/bibliothecary/analyser.rb +32 -188
  9. data/lib/bibliothecary/cli.rb +3 -3
  10. data/lib/bibliothecary/file_info.rb +2 -0
  11. data/lib/bibliothecary/multi_parsers/bundler_like_manifest.rb +22 -0
  12. data/lib/bibliothecary/multi_parsers/cyclonedx.rb +138 -0
  13. data/lib/bibliothecary/multi_parsers/json_runtime.rb +16 -0
  14. data/lib/bibliothecary/parsers/bower.rb +2 -2
  15. data/lib/bibliothecary/parsers/cargo.rb +4 -2
  16. data/lib/bibliothecary/parsers/carthage.rb +6 -6
  17. data/lib/bibliothecary/parsers/clojars.rb +2 -2
  18. data/lib/bibliothecary/parsers/cocoapods.rb +5 -4
  19. data/lib/bibliothecary/parsers/conda.rb +11 -5
  20. data/lib/bibliothecary/parsers/cpan.rb +2 -2
  21. data/lib/bibliothecary/parsers/cran.rb +3 -1
  22. data/lib/bibliothecary/parsers/dub.rb +3 -2
  23. data/lib/bibliothecary/parsers/elm.rb +2 -1
  24. data/lib/bibliothecary/parsers/generic.rb +1 -1
  25. data/lib/bibliothecary/parsers/go.rb +13 -11
  26. data/lib/bibliothecary/parsers/hackage.rb +4 -2
  27. data/lib/bibliothecary/parsers/haxelib.rb +1 -0
  28. data/lib/bibliothecary/parsers/hex.rb +6 -4
  29. data/lib/bibliothecary/parsers/julia.rb +2 -2
  30. data/lib/bibliothecary/parsers/maven.rb +19 -11
  31. data/lib/bibliothecary/parsers/meteor.rb +1 -0
  32. data/lib/bibliothecary/parsers/npm.rb +7 -5
  33. data/lib/bibliothecary/parsers/nuget.rb +10 -7
  34. data/lib/bibliothecary/parsers/packagist.rb +4 -2
  35. data/lib/bibliothecary/parsers/pub.rb +2 -2
  36. data/lib/bibliothecary/parsers/pypi.rb +11 -9
  37. data/lib/bibliothecary/parsers/rubygems.rb +7 -4
  38. data/lib/bibliothecary/parsers/shard.rb +2 -2
  39. data/lib/bibliothecary/parsers/swift_pm.rb +4 -2
  40. data/lib/bibliothecary/runner.rb +8 -3
  41. data/lib/bibliothecary/version.rb +1 -1
  42. data/lib/bibliothecary.rb +3 -0
  43. metadata +22 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84298cce932d2116b7afc6933215c4c3edefb9228a90436df6eca8ffbe8b3106
4
- data.tar.gz: f6e1573273acee5017fd38690293d5850daaab5e5d3c5f20e5c4e6d6f53e1e13
3
+ metadata.gz: 50ba5ddf48a3b7d1051272fef2bcef8ca7bca465b13e251fe9656ae8751f1353
4
+ data.tar.gz: 40b88cb18308c0d624f00eedb67d1a5a946e42d2f3e242436fa376dd54b2c55a
5
5
  SHA512:
6
- metadata.gz: 484603f946e892acfb3e00eff7feb2c9f8adc18f7c4c2d7ca7fc918c64550637b683fb303b8978b1f5b7965e577ecf9ac56ae8030e247c8898963d42bc52b304
7
- data.tar.gz: a5c95805419f43064fb43e828f3ddd2ca486170801c27fe387a611bbcc2a84d8906d48b3044ecfea462aa362d405d9fb4797e2bcb88dc77a3bb742541a90e131
6
+ metadata.gz: 51675c6be455da5a996fe92f703245aac5f3a148eb65aed33bc48675e1a94677da65e3de1651d28cc511391d241e44bc774b287ff6ae2b27d7494037389f0391
7
+ data.tar.gz: 74ceaa45f07deaa972be3a50e5da4e91b2507be75f6a8cf7b46cd8c02319690063e4a6f52f394fafe918debba2af765efceb13e78195de8a827997a343b8128c
data/.circleci/config.yml CHANGED
@@ -5,7 +5,7 @@ orbs:
5
5
  jobs:
6
6
  test:
7
7
  docker:
8
- - image: circleci/ruby:2.6.6-stretch-node
8
+ - image: cimg/ruby:2.7.1
9
9
  executor: ruby/default
10
10
  steps:
11
11
  - checkout
data/README.md CHANGED
@@ -7,6 +7,8 @@ Dependency manifest parsing library for https://libraries.io
7
7
 
8
8
  ## Installation
9
9
 
10
+ Requires Ruby 2.7 or above.
11
+
10
12
  Add this line to your application's Gemfile:
11
13
 
12
14
  ```ruby
@@ -90,6 +92,11 @@ All available config options are in: https://github.com/librariesio/bibliothecar
90
92
  - paket.lock
91
93
  - *.csproj
92
94
  - project.assets.json
95
+ - CycloneDX
96
+ - XML as cyclonedx.xml
97
+ - JSON as cyclonedx.json
98
+ - Note that CycloneDX manifests can contain information on multiple
99
+ package manager's packages!
93
100
  - Bower
94
101
  - bower.json
95
102
  - CPAN
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency "commander"
28
28
  spec.add_dependency "strings-ansi"
29
29
  spec.add_dependency "strings"
30
+ spec.add_dependency "packageurl-ruby"
30
31
 
31
32
  spec.add_development_dependency "pry"
32
33
  spec.add_development_dependency "rake", "~> 12.0"
@@ -0,0 +1,110 @@
1
+ module Bibliothecary
2
+ module Analyser
3
+ module Analysis
4
+ # Convenience method to create FileInfo objects from folder path and
5
+ # file list.
6
+ #
7
+ # @param folder_path [String]
8
+ # @param file_list [Array<String>]
9
+ # @param options [Hash]
10
+ def analyse(folder_path, file_list, options: {})
11
+ analyse_file_info(file_list.map { |full_path| FileInfo.new(folder_path, full_path) }, options: options)
12
+ end
13
+ alias analyze analyse
14
+
15
+ # Analyze a set of FileInfo objects and extract manifests from them all.
16
+ #
17
+ # @params file_info_list [Array<FileInfo>]
18
+ def analyse_file_info(file_info_list, options: {})
19
+ matching_info = file_info_list
20
+ .select(&method(:match_info?))
21
+
22
+ matching_info.flat_map do |info|
23
+ analyse_contents_from_info(info, options: options)
24
+ .merge(related_paths: related_paths(info, matching_info))
25
+ end
26
+ end
27
+ alias analyze_file_info analyse_file_info
28
+
29
+ def analyse_contents(filename, contents, options: {})
30
+ analyse_contents_from_info(FileInfo.new(nil, filename, contents), options: options)
31
+ end
32
+ alias analyze_contents analyse_contents
33
+
34
+ # This is the place a parsing operation will eventually end up.
35
+ #
36
+ # @param info [FileInfo]
37
+ def analyse_contents_from_info(info, options: {})
38
+ # If your Parser needs to return multiple responses for one file, please override this method
39
+ # For example see conda.rb
40
+ kind = determine_kind_from_info(info)
41
+ dependencies = parse_file(info.relative_path, info.contents, options: options)
42
+
43
+ dependencies_to_analysis(info, kind, dependencies)
44
+ rescue Bibliothecary::FileParsingError => e
45
+ Bibliothecary::Analyser::create_error_analysis(platform_name, info.relative_path, kind, e.message)
46
+ end
47
+ alias analyze_contents_from_info analyse_contents_from_info
48
+
49
+ def dependencies_to_analysis(info, kind, dependencies)
50
+ dependencies = dependencies || [] # work around any legacy parsers that return nil
51
+ if generic?
52
+ grouped = dependencies.group_by { |dep| dep[:platform] }
53
+ all_analyses = grouped.keys.map do |platform|
54
+ deplatformed_dependencies = grouped[platform].map { |d| d.delete(:platform); d }
55
+ Bibliothecary::Analyser::create_analysis(platform, info.relative_path, kind, deplatformed_dependencies)
56
+ end
57
+ # this is to avoid a larger refactor for the time being. The larger refactor
58
+ # needs to make analyse_contents return multiple analysis, or add another
59
+ # method that can return multiple and deprecate analyse_contents, perhaps.
60
+ raise "File contains zero or multiple platforms, currently must have exactly one" if all_analyses.length != 1
61
+ all_analyses.first
62
+ else
63
+ Bibliothecary::Analyser::create_analysis(platform_name, info.relative_path, kind, dependencies)
64
+ end
65
+ end
66
+
67
+ # Call the matching parse class method for this file with
68
+ # these contents
69
+ def parse_file(filename, contents, options: {})
70
+ details = first_matching_mapping_details(FileInfo.new(nil, filename, contents))
71
+
72
+ # this can be raised if we don't check match?/match_info?,
73
+ # OR don't have the file contents when we check them, so
74
+ # it turns out for example that a .xml file isn't a
75
+ # manifest after all.
76
+ raise Bibliothecary::FileParsingError.new("No parser for this file type", filename) unless details[:parser]
77
+
78
+ # The `parser` method should raise an exception if the file is malformed,
79
+ # should return empty [] if the file is fine but simply doesn't contain
80
+ # any dependencies, and should never return nil. At the time of writing
81
+ # this comment, some of the parsers return [] or nil to mean an error
82
+ # which is confusing to users.
83
+ send(details[:parser], contents, options: options.merge(filename: filename))
84
+
85
+ rescue Exception => e # default is StandardError but C bindings throw Exceptions
86
+ # the C xml parser also puts a newline at the end of the message
87
+ raise Bibliothecary::FileParsingError.new(e.message.strip, filename)
88
+ end
89
+
90
+ private
91
+
92
+ def related_paths(info, infos)
93
+ return [] unless determine_can_have_lockfile_from_info(info)
94
+
95
+ kind = determine_kind_from_info(info)
96
+ relate_to_kind = first_matching_mapping_details(info)
97
+ .fetch(:related_to, %w(manifest lockfile).reject { |k| k == kind })
98
+ dirname = File.dirname(info.relative_path)
99
+
100
+ infos
101
+ .reject { |i| i == info }
102
+ .select { |i| relate_to_kind.include?(determine_kind_from_info(i)) }
103
+ .select { |i| File.dirname(i.relative_path) == dirname }
104
+ .select(&method(:determine_can_have_lockfile_from_info))
105
+ .map(&:relative_path)
106
+ .sort
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,27 @@
1
+ module Bibliothecary
2
+ module Analyser
3
+ module Determinations
4
+ # calling this with contents=nil can produce less-informed
5
+ # results, but kept for back compat
6
+ def determine_kind(filename, contents = nil)
7
+ determine_kind_from_info(FileInfo.new(nil, filename, contents))
8
+ end
9
+
10
+ def determine_kind_from_info(info)
11
+ first_matching_mapping_details(info)
12
+ .fetch(:kind, nil)
13
+ end
14
+
15
+ # calling this with contents=nil can produce less-informed
16
+ # results, but kept for back compat
17
+ def determine_can_have_lockfile(filename, contents = nil)
18
+ determine_can_have_lockfile_from_info(FileInfo.new(nil, filename, contents))
19
+ end
20
+
21
+ def determine_can_have_lockfile_from_info(info)
22
+ first_matching_mapping_details(info)
23
+ .fetch(:can_have_lockfile, true)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,64 @@
1
+ module Bibliothecary
2
+ module Analyser
3
+ module Matchers
4
+ def match_filename(filename, case_insensitive: false)
5
+ if case_insensitive
6
+ lambda { |path| path.downcase == filename.downcase || path.downcase.end_with?("/" + filename.downcase) }
7
+ else
8
+ lambda { |path| path == filename || path.end_with?("/" + filename) }
9
+ end
10
+ end
11
+
12
+ def match_filenames(*filenames)
13
+ lambda do |path|
14
+ filenames.any? { |f| path == f } ||
15
+ filenames.any? { |f| path.end_with?("/" + f) }
16
+ end
17
+ end
18
+
19
+ def match_extension(filename, case_insensitive: false)
20
+ if case_insensitive
21
+ lambda { |path| path.downcase.end_with?(filename.downcase) }
22
+ else
23
+ lambda { |path| path.end_with?(filename) }
24
+ end
25
+ end
26
+
27
+ def mapping_entry_match?(matcher, details, info)
28
+ if matcher.call(info.relative_path)
29
+ # we only want to load contents if we don't have them already
30
+ # and there's a content_matcher method to use
31
+ return true if details[:content_matcher].nil?
32
+ # this is the libraries.io case where we won't load all .xml
33
+ # files (for example) just to look at their contents, we'll
34
+ # assume they are not manifests.
35
+ return false if info.contents.nil?
36
+ return send(details[:content_matcher], info.contents)
37
+ else
38
+ return false
39
+ end
40
+ end
41
+
42
+ # this is broken with contents=nil because it can't look at file
43
+ # contents, so skips manifests that are ambiguously a
44
+ # manifest considering only the filename. However, those are
45
+ # the semantics that libraries.io uses since it doesn't have
46
+ # the files locally.
47
+ def match?(filename, contents = nil)
48
+ match_info?(FileInfo.new(nil, filename, contents))
49
+ end
50
+
51
+ def match_info?(info)
52
+ first_matching_mapping_details(info).any?
53
+ end
54
+
55
+ private
56
+
57
+ def first_matching_mapping_details(info)
58
+ mapping
59
+ .find { |matcher, details| mapping_entry_match?(matcher, details, info) }
60
+ &.last || {}
61
+ end
62
+ end
63
+ end
64
+ end
@@ -1,3 +1,7 @@
1
+ require_relative './analyser/matchers.rb'
2
+ require_relative './analyser/determinations.rb'
3
+ require_relative './analyser/analysis.rb'
4
+
1
5
  module Bibliothecary
2
6
  module Analyser
3
7
  def self.create_error_analysis(platform_name, relative_path, kind, message)
@@ -23,75 +27,34 @@ module Bibliothecary
23
27
 
24
28
  def self.included(base)
25
29
  base.extend(ClassMethods)
30
+
31
+ # Group like-methods into separate modules for easier comprehension.
32
+ base.extend(Bibliothecary::Analyser::Matchers)
33
+ base.extend(Bibliothecary::Analyser::Determinations)
34
+ base.extend(Bibliothecary::Analyser::Analysis)
26
35
  end
27
- module ClassMethods
28
- def generic?
29
- platform_name == "generic"
30
- end
31
36
 
32
- def mapping_entry_match?(matcher, details, info)
33
- if matcher.call(info.relative_path)
34
- # we only want to load contents if we don't have them already
35
- # and there's a content_matcher method to use
36
- return true if details[:content_matcher].nil?
37
- # this is the libraries.io case where we won't load all .xml
38
- # files (for example) just to look at their contents, we'll
39
- # assume they are not manifests.
40
- return false if info.contents.nil?
41
- return send(details[:content_matcher], info.contents)
37
+ module TryCache
38
+ def try_cache(options, key)
39
+ if options[:cache]
40
+ options[:cache][key] ||= yield
41
+
42
+ options[:cache][key]
42
43
  else
43
- return false
44
+ yield
44
45
  end
45
46
  end
47
+ end
46
48
 
47
- def parse_file(filename, contents)
48
- details = first_matching_mapping_details(FileInfo.new(nil, filename, contents))
49
-
50
- # this can be raised if we don't check match?/match_info?,
51
- # OR don't have the file contents when we check them, so
52
- # it turns out for example that a .xml file isn't a
53
- # manifest after all.
54
- raise Bibliothecary::FileParsingError.new("No parser for this file type", filename) unless details[:parser]
55
-
56
- # The `parser` method should raise an exception if the file is malformed,
57
- # should return empty [] if the file is fine but simply doesn't contain
58
- # any dependencies, and should never return nil. At the time of writing
59
- # this comment, some of the parsers return [] or nil to mean an error
60
- # which is confusing to users.
61
- send(details[:parser], contents)
62
-
63
- rescue Exception => e # default is StandardError but C bindings throw Exceptions
64
- # the C xml parser also puts a newline at the end of the message
65
- raise Bibliothecary::FileParsingError.new(e.message.strip, filename)
66
- end
67
-
68
- # this is broken with contents=nil because it can't look at file
69
- # contents, so skips manifests that are ambiguously a
70
- # manifest considering only the filename. However, those are
71
- # the semantics that libraries.io uses since it doesn't have
72
- # the files locally.
73
- def match?(filename, contents = nil)
74
- match_info?(FileInfo.new(nil, filename, contents))
75
- end
76
-
77
- def match_info?(info)
78
- first_matching_mapping_details(info).any?
49
+ module ClassMethods
50
+ def generic?
51
+ platform_name == "generic"
79
52
  end
80
53
 
81
54
  def platform_name
82
55
  self.name.to_s.split('::').last.downcase
83
56
  end
84
57
 
85
- def parse_json_runtime_manifest(file_contents)
86
- JSON.parse(file_contents).fetch('dependencies',[]).map do |name, requirement|
87
- {
88
- name: name,
89
- requirement: requirement,
90
- type: 'runtime'
91
- }
92
- end
93
- end
94
-
95
58
  def map_dependencies(hash, key, type)
96
59
  hash.fetch(key,[]).map do |name, requirement|
97
60
  {
@@ -102,141 +65,22 @@ module Bibliothecary
102
65
  end
103
66
  end
104
67
 
105
- def analyse(folder_path, file_list)
106
- analyse_file_info(file_list.map { |full_path| FileInfo.new(folder_path, full_path) })
107
- end
108
- alias analyze analyse
109
-
110
- def analyse_file_info(file_info_list)
111
- matching_info = file_info_list
112
- .select(&method(:match_info?))
113
-
114
- matching_info.flat_map do |info|
115
- analyse_contents_from_info(info)
116
- .merge(related_paths: related_paths(info, matching_info))
117
- end
118
- end
119
- alias analyze_file_info analyse_file_info
120
-
121
- def analyse_contents(filename, contents)
122
- analyse_contents_from_info(FileInfo.new(nil, filename, contents))
123
- end
124
- alias analyze_contents analyse_contents
125
-
126
- def dependencies_to_analysis(info, kind, dependencies)
127
- dependencies = dependencies || [] # work around any legacy parsers that return nil
128
- if generic?
129
- analyses = []
130
- grouped = dependencies.group_by { |dep| dep[:platform] }
131
- all_analyses = grouped.keys.map do |platform|
132
- deplatformed_dependencies = grouped[platform].map { |d| d.delete(:platform); d }
133
- Bibliothecary::Analyser::create_analysis(platform, info.relative_path, kind, deplatformed_dependencies)
134
- end
135
- # this is to avoid a larger refactor for the time being. The larger refactor
136
- # needs to make analyse_contents return multiple analysis, or add another
137
- # method that can return multiple and deprecate analyse_contents, perhaps.
138
- raise "File contains zero or multiple platforms, currently must have exactly one" if all_analyses.length != 1
139
- all_analyses.first
140
- else
141
- Bibliothecary::Analyser::create_analysis(platform_name, info.relative_path, kind, dependencies)
142
- end
143
- end
144
-
145
- def analyse_contents_from_info(info)
146
- # If your Parser needs to return multiple responses for one file, please override this method
147
- # For example see conda.rb
148
- kind = determine_kind_from_info(info)
149
- dependencies = parse_file(info.relative_path, info.contents)
150
-
151
- dependencies_to_analysis(info, kind, dependencies)
152
- rescue Bibliothecary::FileParsingError => e
153
- Bibliothecary::Analyser::create_error_analysis(platform_name, info.relative_path, kind, e.message)
154
- end
155
- alias analyze_contents_from_info analyse_contents_from_info
68
+ # Add a MultiParser module to a Parser class. This extends the
69
+ # self.mapping method on the parser to include the multi parser's
70
+ # files to watch for, and it extends the Parser class with
71
+ # the multi parser for you.
72
+ #
73
+ # @param klass [Class] A Bibliothecary::MultiParsers class
74
+ def add_multi_parser(klass)
75
+ raise "No mapping found! You should place the add_multi_parser call below def self.mapping." unless respond_to?(:mapping)
156
76
 
157
- # calling this with contents=nil can produce less-informed
158
- # results, but kept for back compat
159
- def determine_kind(filename, contents = nil)
160
- determine_kind_from_info(FileInfo.new(nil, filename, contents))
161
- end
162
-
163
- def determine_kind_from_info(info)
164
- first_matching_mapping_details(info)
165
- .fetch(:kind, nil)
166
- end
167
-
168
- # calling this with contents=nil can produce less-informed
169
- # results, but kept for back compat
170
- def determine_can_have_lockfile(filename, contents = nil)
171
- determine_can_have_lockfile_from_info(FileInfo.new(nil, filename, contents))
172
- end
173
-
174
- def determine_can_have_lockfile_from_info(info)
175
- first_matching_mapping_details(info)
176
- .fetch(:can_have_lockfile, true)
177
- end
77
+ original_mapping = self.mapping
178
78
 
179
- def parse_ruby_manifest(manifest)
180
- manifest.dependencies.inject([]) do |deps, dep|
181
- deps.push({
182
- name: dep.name,
183
- requirement: dep
184
- .requirement
185
- .requirements
186
- .sort_by(&:last)
187
- .map { |op, version| "#{op} #{version}" }
188
- .join(", "),
189
- type: dep.type
190
- })
191
- end.uniq
192
- end
193
-
194
- def match_filename(filename, case_insensitive: false)
195
- if case_insensitive
196
- lambda { |path| path.downcase == filename.downcase || path.downcase.end_with?("/" + filename.downcase) }
197
- else
198
- lambda { |path| path == filename || path.end_with?("/" + filename) }
199
- end
200
- end
201
-
202
- def match_filenames(*filenames)
203
- lambda do |path|
204
- filenames.any? { |f| path == f } ||
205
- filenames.any? { |f| path.end_with?("/" + f) }
79
+ define_singleton_method(:mapping) do
80
+ original_mapping.merge(klass.mapping)
206
81
  end
207
- end
208
-
209
- def match_extension(filename, case_insensitive: false)
210
- if case_insensitive
211
- lambda { |path| path.downcase.end_with?(filename.downcase) }
212
- else
213
- lambda { |path| path.end_with?(filename) }
214
- end
215
- end
216
-
217
- private
218
-
219
- def related_paths(info, infos)
220
- return [] unless determine_can_have_lockfile_from_info(info)
221
-
222
- kind = determine_kind_from_info(info)
223
- relate_to_kind = first_matching_mapping_details(info)
224
- .fetch(:related_to, %w(manifest lockfile).reject { |k| k == kind })
225
- dirname = File.dirname(info.relative_path)
226
-
227
- infos
228
- .reject { |i| i == info }
229
- .select { |i| relate_to_kind.include?(determine_kind_from_info(i)) }
230
- .select { |i| File.dirname(i.relative_path) == dirname }
231
- .select(&method(:determine_can_have_lockfile_from_info))
232
- .map(&:relative_path)
233
- .sort
234
- end
235
82
 
236
- def first_matching_mapping_details(info)
237
- mapping
238
- .find { |matcher, details| mapping_entry_match?(matcher, details, info) }
239
- &.last || {}
83
+ send(:extend, klass)
240
84
  end
241
85
  end
242
86
  end
@@ -18,9 +18,9 @@ module Bibliothecary
18
18
  c.action do |_args, options|
19
19
  options.default path: './'
20
20
  output = Bibliothecary.analyse(options.path)
21
- output.each do |manifest|
22
- puts "#{manifest[:path]} (#{manifest[:platform]})"
23
- manifest[:dependencies].group_by{|d| d[:type] }.each do |type, deps|
21
+ output.each do |file_contents|
22
+ puts "#{file_contents[:path]} (#{manifest[:platform]})"
23
+ file_contents[:dependencies].group_by{|d| d[:type] }.each do |type, deps|
24
24
  puts " #{type}"
25
25
  deps.each do |dep|
26
26
  puts " #{dep[:name]} #{dep[:requirement]}"
@@ -1,6 +1,8 @@
1
1
  require 'pathname'
2
2
 
3
3
  module Bibliothecary
4
+ # A representation of a file on the filesystem, with location information
5
+ # and package manager information if needed.
4
6
  class FileInfo
5
7
  attr_reader :folder_path
6
8
  attr_reader :relative_path
@@ -0,0 +1,22 @@
1
+ module Bibliothecary
2
+ module MultiParsers
3
+ module BundlerLikeManifest
4
+ # this takes parsed Bundler and Bundler-like (CocoaPods)
5
+ # manifests and turns them into a list of dependencies.
6
+ def parse_ruby_manifest(manifest)
7
+ manifest.dependencies.inject([]) do |deps, dep|
8
+ deps.push({
9
+ name: dep.name,
10
+ requirement: dep
11
+ .requirement
12
+ .requirements
13
+ .sort_by(&:last)
14
+ .map { |op, version| "#{op} #{version}" }
15
+ .join(", "),
16
+ type: dep.type
17
+ })
18
+ end.uniq
19
+ end
20
+ end
21
+ end
22
+ end