explain-dependencies 0.0.1
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.
- checksums.yaml +7 -0
- data/README.md +65 -0
- data/bin/xdep +41 -0
- data/lib/xdep.rb +108 -0
- data/lib/xdep/composer.rb +5 -0
- data/lib/xdep/npm.rb +64 -0
- data/lib/xdep/output.rb +44 -0
- data/lib/xdep/ruby.rb +129 -0
- data/lib/xdep/version.rb +3 -0
- metadata +68 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2aa688fbd10f5081f121eb0ba23ab37a27db9298
|
4
|
+
data.tar.gz: 89f5fb3afffb575a54a04f1102dcaeee14202a8d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a78e7c7bbd2ab7d2a16190c4f558c7fe4a908125cc3dda44c0a05b9f555118b3665e7039c218c48561dbab8d7d3f7605f05c46f49340698a855445eb9b48cdd7
|
7
|
+
data.tar.gz: 8b4b694b06f0816f11931d4b2df8e3f7c33923dbdb49fce7772ce5c9fe2495cf064f6aeeaeeaeb2f7f19d3889817ff496afa9f239a0e589f3b14bd1c5dc3b61c
|
data/README.md
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Explain Dependencies
|
2
|
+
|
3
|
+
Explains what your project's dependencies are.
|
4
|
+
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
[Ruby](https://www.ruby-lang.org/en/downloads/) is required. Once it's installed run:
|
9
|
+
|
10
|
+
gem install explain-dependencies
|
11
|
+
|
12
|
+
If you're using [Bundler](http://bundler.io/):
|
13
|
+
|
14
|
+
gem "explain-dependencies", :group => :development
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
|
18
|
+
xdep [-ai] [-f format] [file or directory...]
|
19
|
+
-a, --add Add explanations to the dependency file, if possible
|
20
|
+
-i, --ignore-popular Ignore popular dependencies
|
21
|
+
-f, --format=NAME Output format for explanations (csv or source), defaults to csv
|
22
|
+
|
23
|
+
Your dependencies must be installed in order to describe them.
|
24
|
+
|
25
|
+
### Output Formats
|
26
|
+
|
27
|
+
Explain Dependencies can output to CSV or add descriptions to the
|
28
|
+
dependency file.
|
29
|
+
|
30
|
+
CSV output will contain the following columns: Language, Name,
|
31
|
+
Version, Description, Homepage, License
|
32
|
+
|
33
|
+
Source output adds the dependency's description as a comment
|
34
|
+
directly above the line that's including it. This is not supported for
|
35
|
+
all output formats.
|
36
|
+
|
37
|
+
### Examples
|
38
|
+
|
39
|
+
Output a CSV file describing all of your project's dependencies:
|
40
|
+
|
41
|
+
xdep
|
42
|
+
|
43
|
+
Output a CSV file describing dependencies in `package.json`:
|
44
|
+
|
45
|
+
xdep package.json
|
46
|
+
|
47
|
+
Output a `Gemfile` with comments describing dependencies in `Gemfile`:
|
48
|
+
|
49
|
+
bundle exec xdep -f source Gemfile
|
50
|
+
|
51
|
+
Update your project's `Gemfile` with comments describing each
|
52
|
+
dependency, ignoring well-known dependencies:
|
53
|
+
|
54
|
+
bundle exec xdep -aif source Gemfile
|
55
|
+
|
56
|
+
## Supported Projects
|
57
|
+
|
58
|
+
* Ruby: [Bundler](http://bundler.io/v1.15/man/gemfile.5.html), [RubyGems](http://guides.rubygems.org/specification-reference/)
|
59
|
+
* Node: [npm](https://docs.npmjs.com/getting-started/using-a-package.json)
|
60
|
+
|
61
|
+
## TODO
|
62
|
+
|
63
|
+
* More dependency files
|
64
|
+
* Specify CSV columns
|
65
|
+
* Speedup describing package.json
|
data/bin/xdep
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require "xdep"
|
5
|
+
|
6
|
+
options = {
|
7
|
+
:add => false,
|
8
|
+
:format => :csv,
|
9
|
+
:ignore => false
|
10
|
+
}
|
11
|
+
|
12
|
+
parser = OptionParser.new do |opts|
|
13
|
+
opts.banner = "usage: #{File.basename($0)} [-ai] [-f format] [file or directory...]"
|
14
|
+
|
15
|
+
opts.on "-a", "--add", "Add explanations to the dependency file, if possible" do
|
16
|
+
options[:add] = true
|
17
|
+
options[:format] = :source
|
18
|
+
end
|
19
|
+
|
20
|
+
opts.on "-f", "--format=NAME", %w[csv source], "Output format for explanations (csv or source), defaults to csv" do |f|
|
21
|
+
options[:format] = f.to_sym
|
22
|
+
end
|
23
|
+
|
24
|
+
opts.on "-i", "--ignore-popular", "Ignore popular dependencies" do |n|
|
25
|
+
options[:ignore] = true
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on "-v", "--version", "Display version number" do
|
29
|
+
puts "v#{XDep::VERSION}"
|
30
|
+
exit
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
parser.parse!
|
35
|
+
|
36
|
+
sources = ARGV
|
37
|
+
sources = [Dir.pwd] unless sources.any?
|
38
|
+
|
39
|
+
puts "Checking dependencies..."
|
40
|
+
XDep.new(options).process(sources)
|
41
|
+
puts "Done"
|
data/lib/xdep.rb
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "tempfile"
|
3
|
+
|
4
|
+
require "xdep/npm"
|
5
|
+
require "xdep/ruby"
|
6
|
+
require "xdep/version"
|
7
|
+
|
8
|
+
class XDep
|
9
|
+
Error = Class.new(StandardError)
|
10
|
+
|
11
|
+
REPORT_BASENAME = "dependencies".freeze
|
12
|
+
REPORT_EXTENSION = ".explained".freeze
|
13
|
+
|
14
|
+
FORMAT_HANDLERS = {
|
15
|
+
:csv => [ Bundler::CSVOutput, RubyGems::CSVOutput, Npm::CSVOutput ],
|
16
|
+
:source => [ Bundler::GemfileOutput, RubyGems::GemspecOutput ]
|
17
|
+
}
|
18
|
+
|
19
|
+
def initialize(options = nil)
|
20
|
+
@options = options || {}
|
21
|
+
@format = @options[:format] || :csv
|
22
|
+
raise ArgumentError, "Unknown output format: #{@format}" unless FORMAT_HANDLERS.include?(@format)
|
23
|
+
end
|
24
|
+
|
25
|
+
def process(sources)
|
26
|
+
sources = normalize_sources(sources)
|
27
|
+
raise ArgumentError, "No dependency files found" if sources.empty?
|
28
|
+
|
29
|
+
if @format == :csv
|
30
|
+
output_as_csv(sources)
|
31
|
+
else
|
32
|
+
output_as_original(sources)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def output_as_original(sources)
|
39
|
+
handlers = create_handlers(sources)
|
40
|
+
|
41
|
+
sources.each do |source|
|
42
|
+
File.open(source) do |input|
|
43
|
+
output = Tempfile.new(REPORT_BASENAME)
|
44
|
+
begin
|
45
|
+
# TODO: Not sure this is the right interface
|
46
|
+
handlers[File.basename(source)].process(input, output)
|
47
|
+
output.close # On Win files must be closed before moving.
|
48
|
+
dest = source
|
49
|
+
dest += REPORT_EXTENSION unless @options[:add]
|
50
|
+
FileUtils.mv(output.path, dest)
|
51
|
+
ensure
|
52
|
+
output.close unless output.closed?
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def output_as_csv(sources)
|
59
|
+
handlers = create_handlers(sources)
|
60
|
+
report = File.open("#{REPORT_BASENAME}.csv", "w")
|
61
|
+
|
62
|
+
begin
|
63
|
+
sources.each do |source|
|
64
|
+
# Each handler must append
|
65
|
+
report.seek(0, :END)
|
66
|
+
File.open(source) do |input|
|
67
|
+
handlers[File.basename(source)].process(input, report)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
ensure
|
71
|
+
report.close
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def normalize_sources(sources)
|
76
|
+
sources.flat_map do |source|
|
77
|
+
if File.directory?(source)
|
78
|
+
find_sources(source)
|
79
|
+
elsif !File.file?(source)
|
80
|
+
raise ArgumentError, "No such file: #{source}"
|
81
|
+
elsif find_handler(File.basename(source)).nil?
|
82
|
+
raise ArgumentError, "Don't know how to output #{source} in #@format format"
|
83
|
+
else
|
84
|
+
source
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def create_handlers(sources)
|
90
|
+
sources.each_with_object({}) do |source, h|
|
91
|
+
basename = File.basename(source)
|
92
|
+
unless h.include?(basename)
|
93
|
+
klass = find_handler(basename)
|
94
|
+
raise Error, "#{basename} does not support format #@format" unless klass
|
95
|
+
|
96
|
+
h[basename] = klass.new(@options)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def find_handler(filename)
|
102
|
+
FORMAT_HANDLERS[@format].find { |handler| handler.accepts?(filename) }
|
103
|
+
end
|
104
|
+
|
105
|
+
def find_sources(root)
|
106
|
+
Dir[ File.join(root, "*") ].reject { |path| find_handler(File.basename(path)).nil? }
|
107
|
+
end
|
108
|
+
end
|
data/lib/xdep/npm.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require "json"
|
2
|
+
require "xdep/output"
|
3
|
+
|
4
|
+
class XDep
|
5
|
+
module Npm
|
6
|
+
class CSVOutput < XDep::CSVOutput
|
7
|
+
KNOWN_DEPENDENCIES = []
|
8
|
+
|
9
|
+
def self.accepts?(filename)
|
10
|
+
filename == "package.json"
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def known_dependencies
|
16
|
+
KNOWN_DEPENDENCIES
|
17
|
+
end
|
18
|
+
|
19
|
+
def get_rows(input)
|
20
|
+
rows = []
|
21
|
+
|
22
|
+
Dir.chdir(File.dirname(input.path)) do
|
23
|
+
# This is very slow...
|
24
|
+
ls.each do |name, version|
|
25
|
+
info = describe("#{name}@#{version["version"]}")
|
26
|
+
license = info["licenses"] ? format_licenses(info["licenses"]) : info["license"]
|
27
|
+
rows << [ "JavaScript", name, version["version"], info["description"], info["homepage"], license ]
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
rows
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def ls
|
37
|
+
out = `npm ls --depth=0 --json 2>&1`
|
38
|
+
raise Error, "Failed to execute npm: #{out}" unless $?.exitstatus.zero?
|
39
|
+
parse_json(out)["dependencies"]
|
40
|
+
end
|
41
|
+
|
42
|
+
def describe(pkg)
|
43
|
+
# Include license and licenses else we won't get JSON output if one doesn't exist
|
44
|
+
out = `npm view #{pkg} description homepage license licenses --json 2>&1`
|
45
|
+
raise Error, "Failed to execute npm: #{out}" unless $?.exitstatus.zero?
|
46
|
+
parse_json(out)
|
47
|
+
end
|
48
|
+
|
49
|
+
def parse_json(out)
|
50
|
+
JSON.parse(out)
|
51
|
+
rescue JSON::ParserError => e
|
52
|
+
raise Error, "Failed to parse npm output: #{e}"
|
53
|
+
end
|
54
|
+
|
55
|
+
def format_licenses(licenses)
|
56
|
+
licenses.map do |data|
|
57
|
+
s = data["type"]
|
58
|
+
s << " (#{data["url"]})" if data["url"]
|
59
|
+
s
|
60
|
+
end.join(", ")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
data/lib/xdep/output.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require "csv"
|
2
|
+
|
3
|
+
class XDep
|
4
|
+
class Output
|
5
|
+
def initialize(options = nil)
|
6
|
+
@options = options || {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.accepts?(filename)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def known_dependencies(name)
|
16
|
+
[].freeze
|
17
|
+
end
|
18
|
+
|
19
|
+
def ignore?(name)
|
20
|
+
@options[:ignore] == true && known_dependencies.include?(name)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class CSVOutput < Output
|
25
|
+
HEADER = %w[Language Name Version Description Homepage License].freeze
|
26
|
+
|
27
|
+
def process(input, output)
|
28
|
+
rows = get_rows(input)
|
29
|
+
rows.sort_by! { |r| r[1] }
|
30
|
+
|
31
|
+
mode = output.pos.zero? ? "w" : "a"
|
32
|
+
CSV.open(output, mode) do |csv|
|
33
|
+
csv << HEADER if output.pos.zero?
|
34
|
+
rows.each { |r| csv << r }
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def get_rows(input)
|
41
|
+
raise NotImplementedError
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
data/lib/xdep/ruby.rb
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
require "xdep/output"
|
2
|
+
|
3
|
+
class XDep
|
4
|
+
module Ruby
|
5
|
+
module Helper
|
6
|
+
COMMENT = /\A\s*#/
|
7
|
+
GEMFILE = /\A(\s*)gem\s+(["'])(\S+)\2/
|
8
|
+
GEMSPEC = %r|\A(\s*)\w+\.add(?:_development)?_dependency[(\s+]?(["'])(\S+)\2|
|
9
|
+
|
10
|
+
# I think 2.4.0 added MissingSpecError
|
11
|
+
SPEC_MISSING = defined?(Gem::MissingSpecError) ? Gem::MissingSpecError : Gem::LoadError
|
12
|
+
|
13
|
+
# > 50 million downloads according to rubygems.com, with a few exceptions
|
14
|
+
KNOWN_DEPENDENCIES = %w[
|
15
|
+
actionmailer actionpack actionview activemodel activerecord
|
16
|
+
activesupport addressable arel aws-sdk builder
|
17
|
+
bundler coderay coffee-rails coffee-script coffee-script-source
|
18
|
+
daemons diff-lcs erubis eventmachine execjs
|
19
|
+
faraday ffi hike i18n jquery-rails
|
20
|
+
json mail method_source mime-types mini_portile
|
21
|
+
minitest multi_json multipart-post mysql2 net-ssh nokogiri
|
22
|
+
polyglot pry rack rack-test rails
|
23
|
+
railties rake rest-client rspec rspec-core
|
24
|
+
rspec-expectations rspec-mocks rspec-support rubygems-bundler sass
|
25
|
+
sass-rails sinatra slop sprockets sprockets-rails sqlite3
|
26
|
+
thor thread_safe tilt tzinfo
|
27
|
+
uglifier
|
28
|
+
].freeze
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def known_dependencies
|
33
|
+
KNOWN_DEPENDENCIES
|
34
|
+
end
|
35
|
+
|
36
|
+
def output_ruby(input, output, match)
|
37
|
+
last_line = nil
|
38
|
+
input.each_line do |line|
|
39
|
+
if line =~ match
|
40
|
+
lead = $1
|
41
|
+
name = $3
|
42
|
+
|
43
|
+
if !ignore?(name)
|
44
|
+
spec = find_spec(name)
|
45
|
+
next unless spec
|
46
|
+
|
47
|
+
comment = "#{lead}# #{spec.summary}"
|
48
|
+
# Allow for multiple runs without adding the comment over and over
|
49
|
+
# strip to account for changes in indentation
|
50
|
+
output.puts(comment) unless last_line.strip == comment.strip
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
last_line = line
|
55
|
+
output.puts(line)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def find_spec(name)
|
60
|
+
Gem::Specification.find_by_name(name)
|
61
|
+
rescue SPEC_MISSING
|
62
|
+
raise Error, "Cannot find dependency #{name}; is it installed locally?"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
class CSV < XDep::CSVOutput
|
67
|
+
include Helper
|
68
|
+
|
69
|
+
def self.accepts?(filename)
|
70
|
+
Bundler::GemfileOutput.accepts?(filename) || RubyGems::GemspecOutput.accepts?(filename)
|
71
|
+
end
|
72
|
+
|
73
|
+
protected
|
74
|
+
|
75
|
+
def get_rows(input)
|
76
|
+
rows = []
|
77
|
+
|
78
|
+
input.each_line do |line|
|
79
|
+
next if line =~ COMMENT || line !~ GEMFILE && line !~ GEMSPEC
|
80
|
+
next if ignore?($3)
|
81
|
+
|
82
|
+
spec = find_spec($3)
|
83
|
+
if spec.nil?
|
84
|
+
row = [$3, nil, "Gem not found"]
|
85
|
+
else
|
86
|
+
row = [spec.name, spec.version.to_s, spec.summary, spec.homepage, spec.licenses.join(", ")]
|
87
|
+
end
|
88
|
+
|
89
|
+
row.unshift "Ruby"
|
90
|
+
rows << row
|
91
|
+
end
|
92
|
+
|
93
|
+
rows
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
module RubyGems
|
99
|
+
CSVOutput = Ruby::CSV
|
100
|
+
|
101
|
+
class GemspecOutput < Output
|
102
|
+
include Ruby::Helper
|
103
|
+
|
104
|
+
def self.accepts?(filename)
|
105
|
+
(filename =~ /\w\.gemspec\z/) != nil
|
106
|
+
end
|
107
|
+
|
108
|
+
def process(input, output)
|
109
|
+
output_ruby(input, output, GEMSPEC)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
module Bundler
|
115
|
+
CSVOutput = Ruby::CSV
|
116
|
+
|
117
|
+
class GemfileOutput < Output
|
118
|
+
include Ruby::Helper
|
119
|
+
|
120
|
+
def self.accepts?(filename)
|
121
|
+
filename == "Gemfile"
|
122
|
+
end
|
123
|
+
|
124
|
+
def process(input, output)
|
125
|
+
output_ruby(input, output, GEMFILE)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
data/lib/xdep/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: explain-dependencies
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Skye Shaw
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-09-13 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
description: Explains what your project's dependencies are. Export to CSV or add descriptions
|
28
|
+
as code comments.
|
29
|
+
email: skye.shaw@gmail.com
|
30
|
+
executables:
|
31
|
+
- xdep
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files:
|
34
|
+
- README.md
|
35
|
+
files:
|
36
|
+
- README.md
|
37
|
+
- bin/xdep
|
38
|
+
- lib/xdep.rb
|
39
|
+
- lib/xdep/composer.rb
|
40
|
+
- lib/xdep/npm.rb
|
41
|
+
- lib/xdep/output.rb
|
42
|
+
- lib/xdep/ruby.rb
|
43
|
+
- lib/xdep/version.rb
|
44
|
+
homepage: http://github.com/sshaw/explain-dependencies
|
45
|
+
licenses:
|
46
|
+
- MIT
|
47
|
+
metadata: {}
|
48
|
+
post_install_message:
|
49
|
+
rdoc_options: []
|
50
|
+
require_paths:
|
51
|
+
- lib
|
52
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
requirements: []
|
63
|
+
rubyforge_project:
|
64
|
+
rubygems_version: 2.6.12
|
65
|
+
signing_key:
|
66
|
+
specification_version: 4
|
67
|
+
summary: Explains what your project's dependencies are.
|
68
|
+
test_files: []
|