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.
@@ -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
@@ -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
@@ -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"
@@ -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
@@ -0,0 +1,5 @@
1
+ module XDep
2
+ module Composer
3
+
4
+ end
5
+ end
@@ -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
@@ -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
@@ -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
@@ -0,0 +1,3 @@
1
+ class XDep
2
+ VERSION = "0.0.1"
3
+ end
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: []