bibliothecary 7.3.5 → 8.1.0

Sign up to get free protection for your applications and to get access to all the features.
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 +156 -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 +3 -3
  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: c3e4510e233cc6052a908e31f4d60498481c2b940448a2727e8cf89f28fae9b6
4
- data.tar.gz: fe1fe8895bfae3b445b8c36de74a0285e9121a6fa49fde52bc481bc78d3b8931
3
+ metadata.gz: 7ed029f460ea32073ccc2d06f6fb297926686064554b1447a7014ff8588382f1
4
+ data.tar.gz: 70482eb3f0cfe7afb13124904b1e713596d92e92d90cd6267153ff4319e9d356
5
5
  SHA512:
6
- metadata.gz: 203d1b6888ea667b5f0cf661f7282239d1736f70547c734fb890e0f58ac43d429e4193bda6977a88ca880a2d7cfbd5c1989442f171e27aa6d583932d83898109
7
- data.tar.gz: 2fbf1aaee7ba11983db23d163b57d32e2ba195fc2fa1dad71447becba40c3eafabb9a8834a2e5a12a24d42b46424cafc32837ad8c9157478734e1d17c8d9a98d
6
+ metadata.gz: b8a243cb926cf8e49835f11db86199f8fe8d45dc53b1f80a17c9b3c941bb5935e4c8b8c1a897b40704d8c6c3ddc271fd5d3139aa9373a8328eea3a538c383fac
7
+ data.tar.gz: d6dd8f873e0ce3cabf3f526e4e50393c36c1e3c6aacb7b1581bc7720bc022670ecd0cc96114df8d3dcc561341e3ee920d401a578eaf7fe5b066f64a33306d730
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