explain-dependencies 0.0.1

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