explain-dependencies 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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: []
|