bibliothecary 6.3.1 → 6.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba88fac4e7fbc938bf270504f1b41787f314105f237bb1e4e080e5ad82379aef
4
- data.tar.gz: 28de14e318ae6684074ac38e8a7b2f1fb57fa83d21a97e7908d6afe110bb5d22
3
+ metadata.gz: e4a0567c8ec55a4efdce0ef3582ffb085587a4efcc50b91e3f3596444629d60c
4
+ data.tar.gz: 63c5203b34b171ef99ce8d9beac0e05aabc2fcb202c1125a3184a2968ba6faba
5
5
  SHA512:
6
- metadata.gz: a07ec3aeb787fe83e7a4d415f83b5aeefe1649eb590a320a48a90053b26b5bf215e6649799bc5792e9e20e5e6fc920bddd0f43c3e8f08111d34baf2695f64ed9
7
- data.tar.gz: 3da7fe10d6c6e036091a82e84a1cbfa80ef9ee58f37289629fe8076b1c49da38a7dc40d03dfe7b56b3e1a12327dc714bf220576a2ca6b072ab66a7934bf06c0d
6
+ metadata.gz: 4e68baf27de703a993167504612de962db9ae01b8aa8ab84e2de8556074521076fe7ea27dc0e5596c1188dfd747bb541a31898da5c6ca6f2e40d0af2e7adbd09
7
+ data.tar.gz: 25f3d2f9f38bcce1c339851e5b68b1f92f354ecd0b4ea0608f258c69c7a6a0a0376dd4f2e160208c537aa1370aa0298680af6d10c22c4e82c89a14936ed9aa69
@@ -0,0 +1,2 @@
1
+ ci:
2
+ extra_ignore_directories: [ spec/fixtures ]
@@ -1,6 +1,5 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.4.2
4
3
  - 2.5.1
5
4
  cache: bundler
6
5
  before_install:
@@ -27,6 +27,7 @@ Gem::Specification.new do |spec|
27
27
  spec.add_dependency "commander"
28
28
 
29
29
  spec.add_development_dependency "bundler", "~> 1.11"
30
+ spec.add_development_dependency "pry"
30
31
  spec.add_development_dependency "rake", "~> 12.0"
31
32
  spec.add_development_dependency "rspec", "~> 3.0"
32
33
  spec.add_development_dependency "webmock"
@@ -1,55 +1,71 @@
1
1
  require "bibliothecary/version"
2
2
  require "bibliothecary/analyser"
3
3
  require "bibliothecary/configuration"
4
+ require "bibliothecary/runner"
5
+ require "bibliothecary/exceptions"
6
+ require "bibliothecary/file_info"
7
+ require "find"
4
8
 
5
9
  Dir[File.expand_path('../bibliothecary/parsers/*.rb', __FILE__)].each do |file|
6
10
  require file
7
11
  end
8
12
 
9
13
  module Bibliothecary
10
- def self.analyse(path)
11
- cmd = `find #{path} -type f | grep -vE "#{ignored_files_regex}"`
12
- file_list = cmd.split("\n").sort
13
- package_managers.map{|pm| pm.analyse(path, file_list) }.flatten.compact
14
+ def self.analyse(path, ignore_unparseable_files: true)
15
+ runner.analyse(path, ignore_unparseable_files: ignore_unparseable_files)
16
+ end
17
+
18
+ # deprecated; use load_file_info_list.
19
+ def self.load_file_list(path)
20
+ runner.load_file_list(path)
21
+ end
22
+
23
+ def self.init_package_manager(info)
24
+ runner.init_package_manager(info)
25
+ end
26
+
27
+ def self.load_file_info_list(path)
28
+ runner.load_file_info_list(path)
14
29
  end
15
30
 
16
31
  def self.analyse_file(file_path, contents)
17
- package_managers.map do |pm|
18
- pm.analyse_contents(file_path, contents)
19
- end.flatten.uniq.compact
32
+ runner.analyse_file(file_path, contents)
20
33
  end
21
34
 
22
35
  def self.identify_manifests(file_list)
23
- allowed_file_list = file_list.reject{|f| f.start_with?(*ignored_files) }
24
- package_managers.map do |pm|
25
- allowed_file_list.select do |file_path|
26
- pm.match?(file_path)
27
- end
28
- end.flatten.uniq.compact
36
+ runner.identify_manifests(file_list)
29
37
  end
30
38
 
31
39
  def self.package_managers
32
- Bibliothecary::Parsers.constants.map{|c| Bibliothecary::Parsers.const_get(c) }.sort_by{|c| c.to_s.downcase }
40
+ runner.package_managers
33
41
  end
34
42
 
35
- def self.ignored_files
36
- configuration.ignored_files
43
+ def self.ignored_dirs
44
+ configuration.ignored_dirs
37
45
  end
38
46
 
39
- def self.ignored_files_regex
40
- ignored_files.join('|')
47
+ def self.ignored_files
48
+ configuration.ignored_files
41
49
  end
42
50
 
43
51
  class << self
44
52
  attr_writer :configuration
45
53
  end
46
54
 
55
+ def self.runner
56
+ configuration
57
+ @runner
58
+ end
59
+
47
60
  def self.configuration
48
61
  @configuration ||= Configuration.new
62
+ @runner = Runner.new(@configuration)
63
+ @configuration
49
64
  end
50
65
 
51
66
  def self.reset
52
67
  @configuration = Configuration.new
68
+ @runner = Runner.new(@configuration)
53
69
  end
54
70
 
55
71
  def self.configure
@@ -1,20 +1,81 @@
1
1
  module Bibliothecary
2
2
  module Analyser
3
+ def self.create_error_analysis(platform_name, relative_path, kind, message)
4
+ {
5
+ platform: platform_name,
6
+ path: relative_path,
7
+ dependencies: nil,
8
+ kind: kind,
9
+ success: false,
10
+ error_message: message
11
+ }
12
+ end
13
+
14
+ def self.create_analysis(platform_name, relative_path, kind, dependencies)
15
+ {
16
+ platform: platform_name,
17
+ path: relative_path,
18
+ dependencies: dependencies,
19
+ kind: kind,
20
+ success: true
21
+ }
22
+ end
23
+
3
24
  def self.included(base)
4
25
  base.extend(ClassMethods)
5
26
  end
6
27
  module ClassMethods
28
+ def mapping_entry_match?(regex, details, info)
29
+ if info.relative_path.match(regex)
30
+ # we only want to load contents if we don't have them already
31
+ # and there's a content_matcher method to use
32
+ return true if details[:content_matcher].nil?
33
+ # this is the libraries.io case where we won't load all .xml
34
+ # files (for example) just to look at their contents, we'll
35
+ # assume they are not manifests.
36
+ return false if info.contents.nil?
37
+ return send(details[:content_matcher], info.contents)
38
+ else
39
+ return false
40
+ end
41
+ end
42
+
7
43
  def parse_file(filename, contents)
8
44
  mapping.each do |regex, details|
9
- if filename.match(regex)
10
- return send(details[:parser], contents)
45
+ if mapping_entry_match?(regex, details, FileInfo.new(nil, filename, contents))
46
+ begin
47
+ # The `parser` method should raise an exception if the file is malformed,
48
+ # should return empty [] if the file is fine but simply doesn't contain
49
+ # any dependencies, and should never return nil. At the time of writing
50
+ # this comment, some of the parsers return [] or nil to mean an error
51
+ # which is confusing to users.
52
+ return send(details[:parser], contents)
53
+ rescue Exception => e # default is StandardError but C bindings throw Exceptions
54
+ # the C xml parser also puts a newline at the end of the message
55
+ raise Bibliothecary::FileParsingError.new(e.message.strip, filename)
56
+ end
11
57
  end
12
58
  end
13
- return []
59
+ # this can be raised if we don't check match?/match_info?,
60
+ # OR don't have the file contents when we check them, so
61
+ # it turns out for example that a .xml file isn't a
62
+ # manifest after all.
63
+ raise Bibliothecary::FileParsingError.new("No parser for this file type", filename)
64
+ end
65
+
66
+ # this is broken with contents=nil because it can't look at file
67
+ # contents, so skips manifests that are ambiguously a
68
+ # manifest considering only the filename. However, those are
69
+ # the semantics that libraries.io uses since it doesn't have
70
+ # the files locally.
71
+ def match?(filename, contents = nil)
72
+ match_info?(FileInfo.new(nil, filename, contents))
14
73
  end
15
74
 
16
- def match?(filename)
17
- mapping.keys.any?{|regex| filename.match(regex) }
75
+ def match_info?(info)
76
+ mapping.any? do |regex, details|
77
+ mapping_entry_match?(regex, details, info)
78
+ end
18
79
  end
19
80
 
20
81
  def platform_name
@@ -43,15 +104,15 @@ module Bibliothecary
43
104
 
44
105
  def set_related_paths_field(by_dirname_dest, by_dirname_source)
45
106
  by_dirname_dest.each do |dirname, analyses|
46
- analyses.each do |analysis|
47
- source_analyses = by_dirname_source[dirname].map { |source_analysis| source_analysis[:path] }
107
+ analyses.each do |(info, analysis)|
108
+ source_analyses = by_dirname_source[dirname].map { |(info, source_analysis)| info.relative_path }
48
109
  analysis[:related_paths] = source_analyses.sort
49
110
  end
50
111
  end
51
112
  end
52
113
 
53
114
  def add_related_paths(analyses)
54
- analyses.each do |analysis|
115
+ analyses.each do |(info, analysis)|
55
116
  analysis[:related_paths] = []
56
117
  end
57
118
 
@@ -67,13 +128,16 @@ module Bibliothecary
67
128
  "lockfile" => Hash.new { |h, k| h[k] = [] }
68
129
  }
69
130
 
70
- analyses.each do |analysis|
71
- dirname = File.dirname(analysis[:path])
72
- by_dirname[analysis[:kind]][dirname].push(analysis)
131
+ analyses.each do |(info, analysis)|
132
+ dirname = File.dirname(info.relative_path)
133
+ by_dirname[analysis[:kind]][dirname].push([info, analysis])
73
134
  end
74
135
 
75
136
  by_dirname["manifest"].each do |_, manifests|
76
- manifests.delete_if { |manifest| !determine_can_have_lockfile(manifest[:path]) }
137
+ # This determine_can_have_lockfile in theory needs the file contents but
138
+ # in practice doesn't right now since the only mapping that needs
139
+ # file contents is a lockfile and not a manifest so won't reach here.
140
+ manifests.delete_if { |(info, manifest)| !determine_can_have_lockfile_from_info(info) }
77
141
  end
78
142
 
79
143
  set_related_paths_field(by_dirname["manifest"], by_dirname["lockfile"])
@@ -81,44 +145,60 @@ module Bibliothecary
81
145
  end
82
146
 
83
147
  def analyse(folder_path, file_list)
84
- analyses = file_list.map do |path|
85
- filename = path.gsub(folder_path, '').gsub(/^\//, '')
86
- next unless match?(filename)
87
- contents = File.open(path).read
88
- analyse_contents(filename, contents)
89
- end.compact
148
+ analyse_file_info(file_list.map { |full_path| FileInfo.new(folder_path, full_path) })
149
+ end
150
+
151
+ def analyse_file_info(file_info_list)
152
+ analyses = file_info_list.map do |info|
153
+ next unless match_info?(info)
154
+ [info, analyse_contents_from_info(info)]
155
+ end
156
+
157
+ # strip the ones we failed to analyse
158
+ analyses = analyses.reject { |(info, analysis)| analysis.nil? }
90
159
 
91
160
  add_related_paths(analyses)
92
161
 
93
- analyses
162
+ analyses.map { |(info, analysis)| analysis }
94
163
  end
95
164
 
96
165
  def analyse_contents(filename, contents)
97
- dependencies = parse_file(filename, contents)
98
- if dependencies && dependencies.any?
99
- {
100
- platform: platform_name,
101
- path: filename,
102
- dependencies: dependencies,
103
- kind: determine_kind(filename)
104
- }
105
- end
106
- # rescue
107
- # nil
166
+ analyse_contents_from_info(FileInfo.new(nil, filename, contents))
167
+ end
168
+
169
+ def analyse_contents_from_info(info)
170
+ kind = determine_kind_from_info(info)
171
+ dependencies = parse_file(info.relative_path, info.contents)
172
+
173
+ Bibliothecary::Analyser::create_analysis(platform_name, info.relative_path, kind, dependencies || [])
174
+ rescue Bibliothecary::FileParsingError => e
175
+ Bibliothecary::Analyser::create_error_analysis(platform_name, info.relative_path, kind, e.message)
176
+ end
177
+
178
+ # calling this with contents=nil can produce less-informed
179
+ # results, but kept for back compat
180
+ def determine_kind(filename, contents = nil)
181
+ determine_kind_from_info(FileInfo.new(nil, filename, contents))
108
182
  end
109
183
 
110
- def determine_kind(filename)
184
+ def determine_kind_from_info(info)
111
185
  mapping.each do |regex, details|
112
- if filename.match(regex)
186
+ if mapping_entry_match?(regex, details, info)
113
187
  return details[:kind]
114
188
  end
115
189
  end
116
- return []
190
+ return nil
191
+ end
192
+
193
+ # calling this with contents=nil can produce less-informed
194
+ # results, but kept for back compat
195
+ def determine_can_have_lockfile(filename, contents = nil)
196
+ determine_can_have_lockfile_from_info(FileInfo.new(nil, filename, contents))
117
197
  end
118
198
 
119
- def determine_can_have_lockfile(filename)
199
+ def determine_can_have_lockfile_from_info(info)
120
200
  mapping.each do |regex, details|
121
- if filename.match(regex)
201
+ if mapping_entry_match?(regex, details, info)
122
202
  return details.fetch(:can_have_lockfile, true)
123
203
  end
124
204
  end
@@ -1,5 +1,6 @@
1
1
  module Bibliothecary
2
2
  class Configuration
3
+ attr_accessor :ignored_dirs
3
4
  attr_accessor :ignored_files
4
5
  attr_accessor :carthage_parser_host
5
6
  attr_accessor :clojars_parser_host
@@ -10,7 +11,8 @@ module Bibliothecary
10
11
  attr_accessor :cabal_parser_host
11
12
 
12
13
  def initialize
13
- @ignored_files = ['.git', 'node_modules', 'bower_components', 'spec/fixtures', 'vendor', 'dist']
14
+ @ignored_dirs = ['.git', 'node_modules', 'bower_components', 'vendor', 'dist']
15
+ @ignored_files = []
14
16
  @carthage_parser_host = 'https://carthage.libraries.io'
15
17
  @clojars_parser_host = 'https://clojars.libraries.io'
16
18
  @mix_parser_host = 'https://mix.libraries.io'
@@ -0,0 +1,18 @@
1
+ module Bibliothecary
2
+ class RemoteParsingError < StandardError
3
+ attr_accessor :code
4
+ def initialize(msg, code)
5
+ @code = code
6
+ super(msg)
7
+ end
8
+ end
9
+
10
+ class FileParsingError < StandardError
11
+ attr_accessor :filename
12
+ def initialize(msg, filename)
13
+ @filename = filename
14
+ msg = "#{filename}: #{msg}" unless msg.include?(filename)
15
+ super("#{msg}")
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,51 @@
1
+ require 'pathname'
2
+
3
+ module Bibliothecary
4
+ class FileInfo
5
+ attr_reader :folder_path
6
+ attr_reader :relative_path
7
+ attr_reader :full_path
8
+ attr_accessor :package_manager
9
+
10
+ def contents
11
+ @contents ||=
12
+ begin
13
+ if @folder_path.nil?
14
+ # if we have no folder_path then we aren't dealing with a
15
+ # file that's actually on the filesystem
16
+ nil
17
+ else
18
+ File.open(@full_path).read
19
+ end
20
+ end
21
+ end
22
+
23
+ # If the FileInfo represents an actual file on disk,
24
+ # the contents can be nil and lazy-loaded; we allow
25
+ # contents to be passed in here to allow pulling them
26
+ # from somewhere other than the disk.
27
+ def initialize(folder_path, full_path, contents = nil)
28
+ # Note that cleanpath does NOT touch the filesystem,
29
+ # leaving the lazy-load of file contents as the only
30
+ # time we touch the filesystem.
31
+
32
+ full_pathname = Pathname.new(full_path)
33
+ @full_path = full_pathname.cleanpath.to_path
34
+
35
+ if folder_path.nil?
36
+ # this is the case where we e.g. have filenames from the GitHub API
37
+ # and don't have a local folder
38
+ @folder_path = nil
39
+ @relative_path = @full_path
40
+ else
41
+ folder_pathname = Pathname.new(folder_path)
42
+ @folder_path = folder_pathname.cleanpath.to_path
43
+ @relative_path = full_pathname.relative_path_from(folder_pathname).cleanpath.to_path
44
+ end
45
+
46
+ @contents = contents
47
+
48
+ @package_manager = nil
49
+ end
50
+ end
51
+ end
@@ -34,6 +34,7 @@ module Bibliothecary
34
34
 
35
35
  def self.map_dependencies(manifest, path)
36
36
  response = Typhoeus.post("#{Bibliothecary.configuration.carthage_parser_host}/#{path}", params: {body: manifest})
37
+ raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.carthage_parser_host}/#{path}", response.response_code) unless response.success?
37
38
  json = JSON.parse(response.body)
38
39
 
39
40
  json.map do |dependency|
@@ -17,6 +17,7 @@ module Bibliothecary
17
17
 
18
18
  def self.parse_manifest(manifest)
19
19
  response = Typhoeus.post("#{Bibliothecary.configuration.clojars_parser_host}/project.clj", body: manifest)
20
+ raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.clojars_parser_host}/project.clj", response.response_code) unless response.success?
20
21
  json = JSON.parse response.body
21
22
  index = json.index("dependencies")
22
23
 
@@ -26,11 +26,8 @@ module Bibliothecary
26
26
 
27
27
  response = Typhoeus.post("#{Bibliothecary.configuration.cabal_parser_host}/parse", headers: headers, body: file_contents)
28
28
 
29
- if response.response_code == 200 then
30
- JSON.parse(response.body, symbolize_names: true)
31
- else
32
- []
33
- end
29
+ raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.cabal_parser_host}/parse", response.response_code) unless response.success?
30
+ JSON.parse(response.body, symbolize_names: true)
34
31
  end
35
32
 
36
33
  def self.parse_cabal_config(file_contents)
@@ -20,6 +20,7 @@ module Bibliothecary
20
20
 
21
21
  def self.parse_mix(manifest)
22
22
  response = Typhoeus.post("#{Bibliothecary.configuration.mix_parser_host}/", body: manifest)
23
+ raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.mix_parser_host}/", response.response_code) unless response.success?
23
24
  json = JSON.parse response.body
24
25
 
25
26
  json.map do |name, version|
@@ -33,6 +34,7 @@ module Bibliothecary
33
34
 
34
35
  def self.parse_mix_lock(manifest)
35
36
  response = Typhoeus.post("#{Bibliothecary.configuration.mix_parser_host}/lock", body: manifest)
37
+ raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.mix_parser_host}/", response.response_code) unless response.success?
36
38
  json = JSON.parse response.body
37
39
 
38
40
  json.map do |name, info|
@@ -5,6 +5,12 @@ module Bibliothecary
5
5
  class Maven
6
6
  include Bibliothecary::Analyser
7
7
 
8
+ # e.g. "annotationProcessor - Annotation processors and their dependencies for source set 'main'."
9
+ GRADLE_TYPE_REGEX = /^(\w+)/
10
+
11
+ # "| \\--- com.google.guava:guava:23.5-jre (*)"
12
+ GRADLE_DEP_REGEX = /(\+---|\\---){1}/
13
+
8
14
  def self.mapping
9
15
  {
10
16
  /^ivy\.xml$|.*\/ivy\.xml$/i => {
@@ -18,6 +24,15 @@ module Bibliothecary
18
24
  /^build.gradle$|.*\/build.gradle$/i => {
19
25
  kind: 'manifest',
20
26
  parser: :parse_gradle
27
+ },
28
+ /^.+.xml$/i => {
29
+ content_matcher: :ivy_report?,
30
+ kind: 'lockfile',
31
+ parser: :parse_ivy_report
32
+ },
33
+ /^gradle-dependencies-q\.txt|.*\/gradle-dependencies-q\.txt$/i => {
34
+ kind: 'lockfile',
35
+ parser: :parse_gradle_resolved
21
36
  }
22
37
  }
23
38
  end
@@ -32,8 +47,66 @@ module Bibliothecary
32
47
  type: 'runtime'
33
48
  }
34
49
  end
35
- rescue
36
- []
50
+ end
51
+
52
+ def self.ivy_report?(file_contents)
53
+ doc = Ox.parse file_contents
54
+ root = doc&.locate("ivy-report")&.first
55
+ return !root.nil?
56
+ rescue Exception => e
57
+ # We rescue exception here since native libs can throw a non-StandardError
58
+ # We don't want to throw errors during the matching phase, only during
59
+ # parsing after we match.
60
+ false
61
+ end
62
+
63
+ def self.parse_ivy_report(file_contents)
64
+ doc = Ox.parse file_contents
65
+ root = doc.locate("ivy-report").first
66
+ raise "ivy-report document does not have ivy-report at the root" if root.nil?
67
+ info = doc.locate("ivy-report/info").first
68
+ raise "ivy-report document lacks <info> element" if info.nil?
69
+ type = info.attributes[:conf]
70
+ type = "unknown" if type.nil?
71
+ modules = doc.locate("ivy-report/dependencies/module")
72
+ modules.map do |mod|
73
+ attrs = mod.attributes
74
+ org = attrs[:organisation]
75
+ name = attrs[:name]
76
+ version = mod.locate('revision').first&.attributes[:name]
77
+
78
+ next nil if org.nil? or name.nil? or version.nil?
79
+
80
+ {
81
+ name: "#{org}:#{name}",
82
+ requirement: version,
83
+ type: type
84
+ }
85
+ end.compact
86
+ end
87
+
88
+ def self.parse_gradle_resolved(file_contents)
89
+ type = nil
90
+ file_contents.split("\n").map do |line|
91
+ type_match = GRADLE_TYPE_REGEX.match(line)
92
+ type = type_match.captures[0] if type_match
93
+
94
+ gradle_dep_match = GRADLE_DEP_REGEX.match(line)
95
+ next unless gradle_dep_match
96
+
97
+ split = gradle_dep_match.captures[0]
98
+
99
+ # org.springframework.boot:spring-boot-starter-web:2.1.0.M3 (*)
100
+ # Lines can end with (n) or (*) to indicate that something was not resolved (n) or resolved previously (*).
101
+ dep = line.split(split)[1].sub(/\(n\)$/, "").sub(/\(\*\)$/,"").strip.split(":")
102
+ version = dep[-1]
103
+ version = version.split("->")[-1].strip if line.include?("->")
104
+ {
105
+ name: dep[0, dep.length - 1].join(":"),
106
+ requirement: version,
107
+ type: type
108
+ }
109
+ end.compact.uniq {|item| [item[:name], item[:requirement], item[:type]]}
37
110
  end
38
111
 
39
112
  def self.parse_pom_manifest(file_contents)
@@ -51,12 +124,11 @@ module Bibliothecary
51
124
  type: extract_pom_dep_info(xml, dependency, 'scope') || 'runtime'
52
125
  }
53
126
  end
54
- rescue
55
- []
56
127
  end
57
128
 
58
129
  def self.parse_gradle(manifest)
59
130
  response = Typhoeus.post("#{Bibliothecary.configuration.gradle_parser_host}/parse", body: manifest)
131
+ raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.gradle_parser_host}/parse", response.response_code) unless response.success?
60
132
  json = JSON.parse(response.body)
61
133
  return [] unless json['dependencies']
62
134
  json['dependencies'].map do |dependency|
@@ -40,23 +40,31 @@ module Bibliothecary
40
40
  def self.parse_package_lock(file_contents)
41
41
  manifest = JSON.parse(file_contents)
42
42
  manifest.fetch('dependencies',[]).map do |name, requirement|
43
+ if requirement.fetch("dev", false)
44
+ type = 'development'
45
+ else
46
+ type = 'runtime'
47
+ end
43
48
  {
44
49
  name: name,
45
50
  requirement: requirement["version"],
46
- type: 'runtime'
51
+ type: type
47
52
  }
48
53
  end
49
54
  end
50
55
 
51
56
  def self.parse_manifest(file_contents)
52
57
  manifest = JSON.parse(file_contents)
58
+ raise "appears to be a lockfile rather than manifest format" if manifest.key?('lockfileVersion')
53
59
  map_dependencies(manifest, 'dependencies', 'runtime') +
54
60
  map_dependencies(manifest, 'devDependencies', 'development')
55
61
  end
56
62
 
57
63
  def self.parse_yarn_lock(file_contents)
58
64
  response = Typhoeus.post("#{Bibliothecary.configuration.yarn_parser_host}/parse", body: file_contents)
59
- return [] unless response.response_code == 200
65
+
66
+ raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.yarn_parser_host}/parse", response.response_code) unless response.success?
67
+
60
68
  json = JSON.parse(response.body, symbolize_names: true)
61
69
  json.uniq.map do |dep|
62
70
  {
@@ -52,7 +52,7 @@ module Bibliothecary
52
52
  manifest.packages.locate('package').map do |dependency|
53
53
  {
54
54
  name: dependency.id,
55
- requirement: dependency.version,
55
+ requirement: (dependency.version if dependency.respond_to? "version") || "*",
56
56
  type: 'runtime'
57
57
  }
58
58
  end
@@ -65,7 +65,7 @@ module Bibliothecary
65
65
  packages = manifest.locate('ItemGroup/PackageReference').map do |dependency|
66
66
  {
67
67
  name: dependency.Include,
68
- requirement: dependency.Version,
68
+ requirement: (dependency.Version if dependency.respond_to? "Version") || "*",
69
69
  type: 'runtime'
70
70
  }
71
71
  end
@@ -14,6 +14,7 @@ module Bibliothecary
14
14
 
15
15
  def self.parse_package_swift(manifest)
16
16
  response = Typhoeus.post("#{Bibliothecary.configuration.swift_parser_host}/to-json", body: manifest)
17
+ raise Bibliothecary::RemoteParsingError.new("Http Error #{response.response_code} when contacting: #{Bibliothecary.configuration.swift_parser_host}/to-json", response.response_code) unless response.success?
17
18
  json = JSON.parse(response.body)
18
19
  json["dependencies"].map do |dependency|
19
20
  name = dependency['url'].gsub(/^https?:\/\//, '').gsub(/\.git$/,'')
@@ -0,0 +1,98 @@
1
+ module Bibliothecary
2
+ # A class that allows bibliothecary to run with multiple configurations at once, rather than with one global
3
+ class Runner
4
+
5
+ def initialize(configuration)
6
+ @configuration = configuration
7
+ end
8
+
9
+ def analyse(path, ignore_unparseable_files: true)
10
+ info_list = load_file_info_list(path)
11
+
12
+ info_list = info_list.reject { |info| info.package_manager.nil? } if ignore_unparseable_files
13
+
14
+ # Each package manager needs to see its entire list so it can
15
+ # associate related manifests and lockfiles for example.
16
+ analyses = package_managers.map do |pm|
17
+ matching_infos = info_list.select { |info| info.package_manager == pm }
18
+ pm.analyse_file_info(matching_infos)
19
+ end
20
+ analyses = analyses.flatten.compact
21
+
22
+ info_list.select { |info| info.package_manager.nil? }.each do |info|
23
+ analyses.push(Bibliothecary::Analyser::create_error_analysis('unknown', info.relative_path, 'unknown',
24
+ 'No parser for this file type'))
25
+ end
26
+
27
+ analyses
28
+ end
29
+
30
+ # deprecated; use load_file_info_list.
31
+ def load_file_list(path)
32
+ load_file_info_list(path).map { |info| info.full_path }
33
+ end
34
+
35
+ def init_package_manager(info)
36
+ # set the package manager on each info
37
+ matches = package_managers.select { |pm| pm.match_info?(info) }
38
+
39
+ info.package_manager = matches[0] if matches.length == 1
40
+
41
+ # this is a bug at the moment if it's raised (we don't handle it sensibly)
42
+ raise "Multiple package managers fighting over #{info.relative_path}: #{matches.map(&:to_s)}" if matches.length > 1
43
+ end
44
+
45
+ def package_managers
46
+ Bibliothecary::Parsers.constants.map{|c| Bibliothecary::Parsers.const_get(c) }.sort_by{|c| c.to_s.downcase }
47
+ end
48
+
49
+ def load_file_info_list(path)
50
+ file_list = []
51
+ Find.find(path) do |subpath|
52
+ info = FileInfo.new(path, subpath)
53
+ Find.prune if FileTest.directory?(subpath) && ignored_dirs.include?(info.relative_path)
54
+ next unless FileTest.file?(subpath)
55
+ next if ignored_files.include?(info.relative_path)
56
+
57
+ init_package_manager(info)
58
+
59
+ file_list.push(info)
60
+ end
61
+ file_list
62
+ end
63
+
64
+ def analyse_file(file_path, contents)
65
+ package_managers.select { |pm| pm.match?(file_path, contents) }.map do |pm|
66
+ pm.analyse_contents(file_path, contents)
67
+ end.flatten.uniq.compact
68
+ end
69
+
70
+ # this skips manifests sometimes because it doesn't look at file
71
+ # contents and can't establish from only regexes that the thing
72
+ # is a manifest. We exclude rather than include ambiguous filenames
73
+ # because this API is used by libraries.io and we don't want to
74
+ # download all .xml files from GitHub.
75
+ def identify_manifests(file_list)
76
+ ignored_dirs_with_slash = ignored_dirs.map { |d| if d.end_with?("/") then d else d + "/" end }
77
+ allowed_file_list = file_list.reject do |f|
78
+ ignored_dirs.include?(f) || f.start_with?(*ignored_dirs_with_slash)
79
+ end
80
+ allowed_file_list = allowed_file_list.reject{|f| ignored_files.include?(f)}
81
+ package_managers.map do |pm|
82
+ allowed_file_list.select do |file_path|
83
+ # this is a call to match? without file contents, which will skip
84
+ # ambiguous filenames that are only possibly a manifest
85
+ pm.match?(file_path)
86
+ end
87
+ end.flatten.uniq.compact
88
+ end
89
+
90
+ def ignored_dirs
91
+ @configuration.ignored_dirs
92
+ end
93
+
94
+ def ignored_files
95
+ @configuration.ignored_files
96
+ end
97
+ end
98
+ end
@@ -1,3 +1,3 @@
1
1
  module Bibliothecary
2
- VERSION = "6.3.1"
2
+ VERSION = "6.3.2"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bibliothecary
3
3
  version: !ruby/object:Gem::Version
4
- version: 6.3.1
4
+ version: 6.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Nesbitt
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-12 00:00:00.000000000 Z
11
+ date: 2019-03-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: toml-rb
@@ -122,6 +122,20 @@ dependencies:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
124
  version: '1.11'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
125
139
  - !ruby/object:Gem::Dependency
126
140
  name: rake
127
141
  requirement: !ruby/object:Gem::Requirement
@@ -190,13 +204,11 @@ extra_rdoc_files: []
190
204
  files:
191
205
  - ".codeclimate.yml"
192
206
  - ".github/CONTRIBUTING.md"
193
- - ".github/ISSUE_TEMPLATE.md"
194
- - ".github/PULL_REQUEST_TEMPLATE.md"
195
- - ".github/SUPPORT.md"
196
207
  - ".gitignore"
197
208
  - ".rspec"
198
209
  - ".rubocop.yml"
199
210
  - ".ruby-version"
211
+ - ".tidelift.yml"
200
212
  - ".travis.yml"
201
213
  - CODE_OF_CONDUCT.md
202
214
  - Gemfile
@@ -212,6 +224,8 @@ files:
212
224
  - lib/bibliothecary/analyser.rb
213
225
  - lib/bibliothecary/cli.rb
214
226
  - lib/bibliothecary/configuration.rb
227
+ - lib/bibliothecary/exceptions.rb
228
+ - lib/bibliothecary/file_info.rb
215
229
  - lib/bibliothecary/parsers/bower.rb
216
230
  - lib/bibliothecary/parsers/cargo.rb
217
231
  - lib/bibliothecary/parsers/carthage.rb
@@ -236,6 +250,7 @@ files:
236
250
  - lib/bibliothecary/parsers/rubygems.rb
237
251
  - lib/bibliothecary/parsers/shard.rb
238
252
  - lib/bibliothecary/parsers/swift_pm.rb
253
+ - lib/bibliothecary/runner.rb
239
254
  - lib/bibliothecary/version.rb
240
255
  - lib/sdl_parser.rb
241
256
  homepage: https://github.com/librariesio/bibliothecary
@@ -1,18 +0,0 @@
1
- Thanks for taking the time to raise an issue. This template should guide you through the process of submitting a bug, enhancement or feature request. Please erase any part of this template that is not relevant to your issue.
2
-
3
- ## Bugs
4
- Before submitting a bug report:
5
-
6
- - [ ] Double-check that the bug is persistent,
7
- - [ ] Double-check the bug hasn't already been reported [on our issue tracker](https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+org%3Alibrariesio), they *should* be labelled `bug` or `bugsnag`.
8
-
9
- If you have completed those steps then please replace this section with a description of the steps taken to recreate the bug, the expected behavior and the observed behavior.
10
-
11
- ## Enhancements and Features
12
-
13
- Before submitting an enhancement or feature request:
14
-
15
- - [ ] Check that the enhancement is not already [in our issue tracker](https://github.com/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue+org%3Alibrariesio), they should be labelled 'enhancement'.,
16
- - [ ] For large feature requests, check that your request aligns with our strategy http://docs.libraries.io/strategy.
17
-
18
- If you have complete the above step then please replace this section with a description of your proposed enhancement or feature, the motivation for it, an approach and any alternative approaches considered, and whether you are willing and able to create a pull request for it. Note that we may close this issue if it's not something we're planning on working on.
@@ -1,10 +0,0 @@
1
- Thanks taking the time to contribute. This template should help guide you through the process of creating a pull request for review. Please erase any part of this template that is not relevant to your pull request:
2
-
3
-
4
- - [ ] Have you followed the guidelines for [contributors](http://docs.libraries.io/contributorshandbook)?
5
- - [ ] Have you checked to ensure there aren't other open pull requests on the repository for a similar change?
6
- - [ ] Is there a corresponding ticket for your pull request?
7
- - [ ] Have you written new tests for your changes?
8
- - [ ] Have you successfully run the project with your changes locally?
9
-
10
- If so then please replace this section with a link to the ticket(s) it addressed, an explanation of your change and why you think we should include it. Thanks again!
@@ -1,10 +0,0 @@
1
- # Libraries.io Support
2
-
3
- If you're looking for support for Libraries.io there are a lot of options, check out:
4
-
5
- * Documentation &mdash; https://docs.libraries.io
6
- * Email &mdash; support@libraries.io
7
- * Twitter &mdash; https://twitter.com/librariesio
8
- * Chat &mdash; https://slack.libraries.io
9
-
10
- On Discuss and in the Libraries.io Slack team, there are a bunch of helpful community members that should be willing to point you in the right direction.