Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Go Modules version and dependency support! #2528

Merged
merged 11 commits into from
Jun 17, 2020
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ gem "brotli"
gem "bundler"
gem "charlock_holmes", ">= 0.7.5"
gem "chartkick"
gem "chronic"
gem "commonmarker"
gem "concurrent-ruby-ext"
gem "counter_culture"
Expand Down
20 changes: 11 additions & 9 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ GEM
ast (2.4.0)
autoprefixer-rails (9.5.1.1)
execjs
bibliothecary (6.7.4)
bibliothecary (6.8.7)
commander
deb_control
librariesio-gem-parser
Expand Down Expand Up @@ -130,10 +130,11 @@ GEM
activesupport
charlock_holmes (0.7.6)
chartkick (3.3.1)
chronic (0.10.2)
citrus (3.0.2)
cliver (0.3.2)
coderay (1.1.2)
commander (4.4.7)
commander (4.5.2)
highline (~> 2.0.0)
commonmarker (0.20.1)
ruby-enum (~> 0.5)
Expand Down Expand Up @@ -200,7 +201,7 @@ GEM
rake
rake-compiler
fast_xs (0.8.0)
ffi (1.11.1)
ffi (1.13.0)
fog-aws (3.5.0)
fog-core (~> 2.1)
fog-json (~> 1.1)
Expand Down Expand Up @@ -306,7 +307,7 @@ GEM
googleapis-common-protos-types (~> 1.0)
hashdiff (0.4.0)
hashie (3.6.0)
highline (2.0.2)
highline (2.0.3)
hiredis (0.6.3)
htmlentities (4.3.4)
httparty (0.17.0)
Expand Down Expand Up @@ -406,7 +407,7 @@ GEM
org-ruby (0.9.12)
rubypants (~> 0.2)
os (1.0.1)
ox (2.11.0)
ox (2.13.2)
parallel (1.17.0)
parser (2.7.0.4)
ast (~> 2.4.0)
Expand Down Expand Up @@ -614,11 +615,11 @@ GEM
google-cloud-trace (~> 0.33)
stackdriver-core (1.3.3)
google-cloud-core (~> 1.2)
strings (0.1.5)
strings (0.1.8)
strings-ansi (~> 0.1)
unicode-display_width (~> 1.5)
unicode_utils (~> 1.4)
strings-ansi (0.1.0)
strings-ansi (0.2.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
thor (0.19.4)
Expand All @@ -632,14 +633,14 @@ GEM
turbolinks (5.2.0)
turbolinks-source (~> 5.2)
turbolinks-source (5.2.0)
typhoeus (1.3.1)
typhoeus (1.4.0)
ethon (>= 0.9.0)
tzinfo (1.2.6)
thread_safe (~> 0.1)
uber (0.1.0)
uglifier (4.1.20)
execjs (>= 0.3.0, < 3)
unicode-display_width (1.6.0)
unicode-display_width (1.6.1)
unicode_utils (1.4.0)
vcr (4.0.0)
webmock (3.5.1)
Expand Down Expand Up @@ -674,6 +675,7 @@ DEPENDENCIES
bundler
charlock_holmes (>= 0.7.5)
chartkick
chronic
commonmarker
concurrent-ruby-ext
counter_culture
Expand Down
82 changes: 69 additions & 13 deletions app/models/package_manager/go.rb
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# frozen_string_literal: true

module PackageManager
class Go < Base
HAS_VERSIONS = false
HAS_DEPENDENCIES = false
HAS_VERSIONS = true
HAS_DEPENDENCIES = true
BIBLIOTHECARY_SUPPORT = true
URL = 'http://go-search.org/'
URL = 'https://pkg.go.dev/'
COLOR = '#375eab'
KNOWN_HOSTS = [
'bitbucket.org',
Expand All @@ -21,32 +23,86 @@ class Go < Base


def self.package_link(project, version = nil)
"http://go-search.org/view?id=#{project.name}"
"https://pkg.go.dev/#{project.name}#{"@#{version}" if version}"
end

def self.documentation_url(name, version = nil)
"http://godoc.org/#{name}"
"https://pkg.go.dev/#{name}#{"@#{version}" if version}?tab=doc"
end

def self.install_instructions(project, version = nil)
"go get #{project.name}"
end

def self.project_names
katzj marked this conversation as resolved.
Show resolved Hide resolved
get("http://go-search.org/api?action=packages")
# Currently the index only shows the last <=2000 package version releases from the date given. (https://proxy.golang.org/)
project_window = 1.day.ago.strftime("%FT%TZ")
get_raw("https://index.golang.org/index?since=#{project_window}&limit=2000")
.lines
.map { |line| JSON.parse(line)["Path"] }
end

def self.project(name)
get("http://go-search.org/api?action=package&id=#{name}")
if doc_html = get_html("https://pkg.go.dev/#{name}?tab=doc")
overview_html = get_html("https://pkg.go.dev/#{name}?tab=overview")

# NB fetching versions from the html only gets dates without timestamps, but we could alternatively use the go proxy too:
# 1) Fetch the list of versions: http://proxy.golang.org/#{module_name}/@v/list
# 2) And for each version, fetch http://proxy.golang.org/#{module_name}/@v/#{v}.info
versions_html = get_html("https://pkg.go.dev/#{name}?tab=versions")

# Some package pages don't have a Versions tab, but the parent module page may have the Versions tab (e.g. golang.org/x/tools)
if versions_html&.css('.Versions-item').size.zero? && (mod_path = doc_html.css('a[data-test-id="DetailsHeader-infoLabelModule"]').first&.attr('href'))
versions_html = get_html("https://pkg.go.dev/#{mod_path}?tab=versions")
end

{ name: name, html: doc_html, overview_html: overview_html, versions_html: versions_html }
else
{ name: name }
end
end

def self.versions(project, name)
return [] if project.nil?
project[:versions_html]&.css('.Versions-item')&.map do |v|
{ number: v.css('a').first.text, published_at: Chronic.parse(v.css('.Versions-commitTime').first.text) }
end
end

def self.mapping(project)
{
name: project['Package'],
description: project['Synopsis'],
homepage: project['ProjectURL'],
repository_url: get_repository_url(project)
}
if project[:html]
{
name: project[:name],
description: project[:html].css('.Documentation-overview p').map(&:text).join("\n").strip,
licenses: project[:html].css('*[data-test-id="DetailsHeader-infoLabelLicense"] a').map(&:text).join(","),
repository_url: project[:overview_html]&.css('.Overview-sourceCodeLink a')&.first&.text,
homepage: project[:overview_html]&.css('.Overview-sourceCodeLink a')&.first&.text,
versions: project[:versions_html]&.css('.Versions-item')&.map do |v|
{ number: v.css('a').first.text, published_at: Chronic.parse(v.css('.Versions-commitTime').first.text) }
end
}
else
{ name: project[:name] }
end
end

def self.dependencies(name, version, _project)
# Go proxy spec: https://golang.org/cmd/go/#hdr-Module_proxy_protocol
resp = request("https://proxy.golang.org/#{name}/@v/#{version}.mod")
if resp.status == 200
go_mod_file = resp.body
Bibliothecary::Parsers::Go.parse_go_mod(go_mod_file)
.map do |dep|
{
project_name: dep[:name],
requirements: dep[:requirement],
kind: dep[:type],
platform: "Go",
}
end
else
[]
end
end

# https://golang.org/cmd/go/#hdr-Import_path_syntax
Expand Down
10 changes: 3 additions & 7 deletions spec/models/package_manager/go_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,17 @@

describe '#package_link' do
it 'returns a link to project website' do
expect(described_class.package_link(project)).to eq("http://go-search.org/view?id=foo")
expect(described_class.package_link(project)).to eq("https://pkg.go.dev/foo")
end

it 'ignores version' do
expect(described_class.package_link(project, '2.0.0')).to eq("http://go-search.org/view?id=foo")
expect(described_class.package_link(project, '2.0.0')).to eq("https://pkg.go.dev/foo@2.0.0")
end
end

describe '#documentation_url' do
it 'returns a link to project website' do
expect(described_class.documentation_url('foo')).to eq("http://godoc.org/foo")
end

it 'ignores version' do
expect(described_class.documentation_url('foo', '2.0.0')).to eq("http://godoc.org/foo")
expect(described_class.documentation_url('foo', '2.0.0')).to eq("https://pkg.go.dev/foo@2.0.0?tab=doc")
end
end

Expand Down
52 changes: 0 additions & 52 deletions spec/models/package_manager/maven_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,58 +52,6 @@
end
end

describe '#parse_names' do
it 'returns parsed named from HTML' do
html = "
<table>
<tr>
<td>groupid</td>
<td>artifactid</td>
</tr>
<tr>
<td>javax.faces</td>
<td>javax.faces-api</td>
<td>1.0.0</td>
</tr>
<tr>
<td>org.scala-lang</td>
<td>scala-library</td>
<td>2.5.3</td>
</tr>
</table>
"
parsed_names = described_class.parse_names(Nokogiri::HTML.fragment(html))

expect(parsed_names.include?('javax.faces:javax.faces-api')).to be true
expect(parsed_names.include?('org.scala-lang:scala-library')).to be true
end

it 'excludes invalid names from HTML' do
html = "
<table>
<tr>
<td>groupid</td>
<td>artifactid</td>
</tr>
<tr>
<td>javax.faces</td>
<td>javax.faces-api</td>
<td>1.0.0</td>
</tr>
<tr>
<td></td>
<td>mysql</td>
<td>2.5.3</td>
</tr>
</table>
"
parsed_names = described_class.parse_names(Nokogiri::HTML.fragment(html))

expect(parsed_names.include?('javax.faces:javax.faces-api')).to be true
expect(parsed_names.include?('mysql')).to be false
end
end

describe 'mapping_from_pom_xml' do
let(:pom) { Ox.parse(File.open("spec/fixtures/proto-google-common-protos-0.1.9.pom").read) }
let(:parent_pom) { Ox.parse('<project><licenses><license><name>unknown</name></license></licenses><url>https://github.com/googleapis/googleapis</url></project>') }
Expand Down