From 24b105e2e1339fd65cc43727e550edb004277383 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 17 May 2017 11:27:30 -0500 Subject: [PATCH 1/2] Add auxiliary blob viewers to dependency files --- app/models/blob.rb | 14 +++++- app/models/blob_viewer/cartfile.rb | 15 +++++++ app/models/blob_viewer/composer_json.rb | 23 ++++++++++ app/models/blob_viewer/dependency_manager.rb | 43 +++++++++++++++++++ app/models/blob_viewer/gemfile.rb | 15 +++++++ app/models/blob_viewer/gemspec.rb | 27 ++++++++++++ app/models/blob_viewer/godeps_json.rb | 15 +++++++ app/models/blob_viewer/package_json.rb | 23 ++++++++++ app/models/blob_viewer/podfile.rb | 15 +++++++ app/models/blob_viewer/podspec.rb | 27 ++++++++++++ app/models/blob_viewer/podspec_json.rb | 9 ++++ app/models/blob_viewer/requirements_txt.rb | 15 +++++++ app/models/blob_viewer/yarn_lock.rb | 15 +++++++ .../viewers/_dependency_manager.html.haml | 11 +++++ .../projects/blob/viewers/_license.html.haml | 2 +- .../blob/viewers/_loading_auxiliary.html.haml | 2 +- lib/gitlab/file_detector.rb | 22 ++++++++-- .../features/projects/blobs/blob_show_spec.rb | 38 +++++++++++++++- spec/models/blob_viewer/composer_json_spec.rb | 25 +++++++++++ spec/models/blob_viewer/gemspec_spec.rb | 25 +++++++++++ spec/models/blob_viewer/package_json_spec.rb | 25 +++++++++++ spec/models/blob_viewer/podspec_json_spec.rb | 25 +++++++++++ spec/models/blob_viewer/podspec_spec.rb | 25 +++++++++++ 23 files changed, 449 insertions(+), 7 deletions(-) create mode 100644 app/models/blob_viewer/cartfile.rb create mode 100644 app/models/blob_viewer/composer_json.rb create mode 100644 app/models/blob_viewer/dependency_manager.rb create mode 100644 app/models/blob_viewer/gemfile.rb create mode 100644 app/models/blob_viewer/gemspec.rb create mode 100644 app/models/blob_viewer/godeps_json.rb create mode 100644 app/models/blob_viewer/package_json.rb create mode 100644 app/models/blob_viewer/podfile.rb create mode 100644 app/models/blob_viewer/podspec.rb create mode 100644 app/models/blob_viewer/podspec_json.rb create mode 100644 app/models/blob_viewer/requirements_txt.rb create mode 100644 app/models/blob_viewer/yarn_lock.rb create mode 100644 app/views/projects/blob/viewers/_dependency_manager.html.haml create mode 100644 spec/models/blob_viewer/composer_json_spec.rb create mode 100644 spec/models/blob_viewer/gemspec_spec.rb create mode 100644 spec/models/blob_viewer/package_json_spec.rb create mode 100644 spec/models/blob_viewer/podspec_json_spec.rb create mode 100644 spec/models/blob_viewer/podspec_spec.rb diff --git a/app/models/blob.rb b/app/models/blob.rb index 5ae35d3ab08..bc4292f0e9c 100644 --- a/app/models/blob.rb +++ b/app/models/blob.rb @@ -42,7 +42,19 @@ class Blob < SimpleDelegator BlobViewer::License, BlobViewer::Contributing, - BlobViewer::Changelog + BlobViewer::Changelog, + + BlobViewer::Cartfile, + BlobViewer::ComposerJson, + BlobViewer::Gemfile, + BlobViewer::Gemspec, + BlobViewer::GodepsJson, + BlobViewer::PackageJson, + BlobViewer::Podfile, + BlobViewer::Podspec, + BlobViewer::PodspecJson, + BlobViewer::RequirementsTxt, + BlobViewer::YarnLock ].freeze attr_reader :project diff --git a/app/models/blob_viewer/cartfile.rb b/app/models/blob_viewer/cartfile.rb new file mode 100644 index 00000000000..d8471bc33c0 --- /dev/null +++ b/app/models/blob_viewer/cartfile.rb @@ -0,0 +1,15 @@ +module BlobViewer + class Cartfile < DependencyManager + include Static + + self.file_types = %i(cartfile) + + def manager_name + 'Carthage' + end + + def manager_url + 'https://github.com/Carthage/Carthage' + end + end +end diff --git a/app/models/blob_viewer/composer_json.rb b/app/models/blob_viewer/composer_json.rb new file mode 100644 index 00000000000..ef8b4aef8e8 --- /dev/null +++ b/app/models/blob_viewer/composer_json.rb @@ -0,0 +1,23 @@ +module BlobViewer + class ComposerJson < DependencyManager + include ServerSide + + self.file_types = %i(composer_json) + + def manager_name + 'Composer' + end + + def manager_url + 'https://getcomposer.com/' + end + + def package_name + @package_name ||= package_name_from_json('name') + end + + def package_url + "https://packagist.org/packages/#{package_name}" + end + end +end diff --git a/app/models/blob_viewer/dependency_manager.rb b/app/models/blob_viewer/dependency_manager.rb new file mode 100644 index 00000000000..a8d9be945dc --- /dev/null +++ b/app/models/blob_viewer/dependency_manager.rb @@ -0,0 +1,43 @@ +module BlobViewer + class DependencyManager < Base + include Auxiliary + + self.partial_name = 'dependency_manager' + self.binary = false + + def manager_name + raise NotImplementedError + end + + def manager_url + raise NotImplementedError + end + + def package_type + 'package' + end + + def package_name + nil + end + + def package_url + nil + end + + private + + def package_name_from_json(key) + prepare! + + JSON.parse(blob.data)[key] rescue nil + end + + def package_name_from_method_call(name) + prepare! + + match = blob.data.match(/#{name}\s*=\s*["'](?[^"']+)["']/) + match[:name] if match + end + end +end diff --git a/app/models/blob_viewer/gemfile.rb b/app/models/blob_viewer/gemfile.rb new file mode 100644 index 00000000000..fae8c8df23f --- /dev/null +++ b/app/models/blob_viewer/gemfile.rb @@ -0,0 +1,15 @@ +module BlobViewer + class Gemfile < DependencyManager + include Static + + self.file_types = %i(gemfile gemfile_lock) + + def manager_name + 'Bundler' + end + + def manager_url + 'http://bundler.io/' + end + end +end diff --git a/app/models/blob_viewer/gemspec.rb b/app/models/blob_viewer/gemspec.rb new file mode 100644 index 00000000000..7802edeb754 --- /dev/null +++ b/app/models/blob_viewer/gemspec.rb @@ -0,0 +1,27 @@ +module BlobViewer + class Gemspec < DependencyManager + include ServerSide + + self.file_types = %i(gemspec) + + def manager_name + 'RubyGems' + end + + def manager_url + 'https://rubygems.org/' + end + + def package_type + 'gem' + end + + def package_name + @package_name ||= package_name_from_method_call('name') + end + + def package_url + "https://rubygems.org/gems/#{package_name}" + end + end +end diff --git a/app/models/blob_viewer/godeps_json.rb b/app/models/blob_viewer/godeps_json.rb new file mode 100644 index 00000000000..e19a602603b --- /dev/null +++ b/app/models/blob_viewer/godeps_json.rb @@ -0,0 +1,15 @@ +module BlobViewer + class GodepsJson < DependencyManager + include Static + + self.file_types = %i(godeps_json) + + def manager_name + 'godep' + end + + def manager_url + 'https://github.com/tools/godep' + end + end +end diff --git a/app/models/blob_viewer/package_json.rb b/app/models/blob_viewer/package_json.rb new file mode 100644 index 00000000000..09221efb56c --- /dev/null +++ b/app/models/blob_viewer/package_json.rb @@ -0,0 +1,23 @@ +module BlobViewer + class PackageJson < DependencyManager + include ServerSide + + self.file_types = %i(package_json) + + def manager_name + 'npm' + end + + def manager_url + 'https://www.npmjs.com/' + end + + def package_name + @package_name ||= package_name_from_json('name') + end + + def package_url + "https://www.npmjs.com/package/#{package_name}" + end + end +end diff --git a/app/models/blob_viewer/podfile.rb b/app/models/blob_viewer/podfile.rb new file mode 100644 index 00000000000..507bc734cb4 --- /dev/null +++ b/app/models/blob_viewer/podfile.rb @@ -0,0 +1,15 @@ +module BlobViewer + class Podfile < DependencyManager + include Static + + self.file_types = %i(podfile) + + def manager_name + 'CocoaPods' + end + + def manager_url + 'https://cocoapods.org/' + end + end +end diff --git a/app/models/blob_viewer/podspec.rb b/app/models/blob_viewer/podspec.rb new file mode 100644 index 00000000000..a4c242db3a9 --- /dev/null +++ b/app/models/blob_viewer/podspec.rb @@ -0,0 +1,27 @@ +module BlobViewer + class Podspec < DependencyManager + include ServerSide + + self.file_types = %i(podspec) + + def manager_name + 'CocoaPods' + end + + def manager_url + 'https://cocoapods.org/' + end + + def package_type + 'pod' + end + + def package_name + @package_name ||= package_name_from_method_call('name') + end + + def package_url + "https://cocoapods.org/pods/#{package_name}" + end + end +end diff --git a/app/models/blob_viewer/podspec_json.rb b/app/models/blob_viewer/podspec_json.rb new file mode 100644 index 00000000000..602f4a51fd9 --- /dev/null +++ b/app/models/blob_viewer/podspec_json.rb @@ -0,0 +1,9 @@ +module BlobViewer + class PodspecJson < Podspec + self.file_types = %i(podspec_json) + + def package_name + @package_name ||= package_name_from_json('name') + end + end +end diff --git a/app/models/blob_viewer/requirements_txt.rb b/app/models/blob_viewer/requirements_txt.rb new file mode 100644 index 00000000000..83ac55f61d0 --- /dev/null +++ b/app/models/blob_viewer/requirements_txt.rb @@ -0,0 +1,15 @@ +module BlobViewer + class RequirementsTxt < DependencyManager + include Static + + self.file_types = %i(requirements_txt) + + def manager_name + 'pip' + end + + def manager_url + 'https://pip.pypa.io/' + end + end +end diff --git a/app/models/blob_viewer/yarn_lock.rb b/app/models/blob_viewer/yarn_lock.rb new file mode 100644 index 00000000000..31588ddcbab --- /dev/null +++ b/app/models/blob_viewer/yarn_lock.rb @@ -0,0 +1,15 @@ +module BlobViewer + class YarnLock < DependencyManager + include Static + + self.file_types = %i(yarn_lock) + + def manager_name + 'Yarn' + end + + def manager_url + 'https://yarnpkg.com/' + end + end +end diff --git a/app/views/projects/blob/viewers/_dependency_manager.html.haml b/app/views/projects/blob/viewers/_dependency_manager.html.haml new file mode 100644 index 00000000000..a0f0215a5ff --- /dev/null +++ b/app/views/projects/blob/viewers/_dependency_manager.html.haml @@ -0,0 +1,11 @@ += icon('cubes fw') += succeed '.' do + This project manages its dependencies using + %strong= viewer.manager_name + + - if viewer.package_name + and defines a #{viewer.package_type} named + %strong< + = link_to viewer.package_name, viewer.package_url, target: '_blank', rel: 'noopener noreferrer' + += link_to 'Learn more', viewer.manager_url, target: '_blank', rel: 'noopener noreferrer' diff --git a/app/views/projects/blob/viewers/_license.html.haml b/app/views/projects/blob/viewers/_license.html.haml index 9a79d164692..fb9d0b99d09 100644 --- a/app/views/projects/blob/viewers/_license.html.haml +++ b/app/views/projects/blob/viewers/_license.html.haml @@ -5,4 +5,4 @@ This project is licensed under the = succeed '.' do %strong= license.name -= link_to 'Learn more about this license', license.url, target: '_blank', rel: 'noopener noreferrer' += link_to 'Learn more', license.url, target: '_blank', rel: 'noopener noreferrer' diff --git a/app/views/projects/blob/viewers/_loading_auxiliary.html.haml b/app/views/projects/blob/viewers/_loading_auxiliary.html.haml index 058c74bce0d..c7dc9e3250a 100644 --- a/app/views/projects/blob/viewers/_loading_auxiliary.html.haml +++ b/app/views/projects/blob/viewers/_loading_auxiliary.html.haml @@ -1,2 +1,2 @@ = icon('spinner spin fw') -Loading… +Analyzing file… diff --git a/lib/gitlab/file_detector.rb b/lib/gitlab/file_detector.rb index c6a89597b23..a8cb7fc3fe7 100644 --- a/lib/gitlab/file_detector.rb +++ b/lib/gitlab/file_detector.rb @@ -5,17 +5,33 @@ module Gitlab # a README or a CONTRIBUTING file. module FileDetector PATTERNS = { + # Project files readme: /\Areadme/i, changelog: /\A(changelog|history|changes|news)/i, license: /\A(licen[sc]e|copying)(\..+|\z)/i, contributing: /\Acontributing/i, version: 'version', + avatar: /\Alogo\.(png|jpg|gif)\z/, + + # Configuration files gitignore: '.gitignore', koding: '.koding.yml', - gemfile: /\A(Gemfile|gems\.rb)\z/, gitlab_ci: '.gitlab-ci.yml', - avatar: /\Alogo\.(png|jpg|gif)\z/, - route_map: 'route-map.yml' + route_map: 'route-map.yml', + + # Dependency files + cartfile: /\ACartfile/, + composer_json: 'composer.json', + gemfile: /\A(Gemfile|gems\.rb)\z/, + gemfile_lock: 'Gemfile.lock', + gemspec: /\.gemspec\z/, + godeps_json: 'Godeps.json', + package_json: 'package.json', + podfile: 'Podfile', + podspec_json: /\.podspec\.json\z/, + podspec: /\.podspec\z/, + requirements_txt: /requirements\.txt\z/, + yarn_lock: 'yarn.lock' }.freeze # Returns an Array of file types based on the given paths. diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index 9888624a509..c95003f3226 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -423,7 +423,43 @@ feature 'File blob', :js, feature: true do expect(page).to have_content('This project is licensed under the MIT License.') # shows a learn more link - expect(page).to have_link('Learn more about this license', 'http://choosealicense.com/licenses/mit/') + expect(page).to have_link('Learn more', 'http://choosealicense.com/licenses/mit/') + end + end + end + + context '*.gemspec' do + before do + project.add_master(project.creator) + + Files::CreateService.new( + project, + project.creator, + start_branch: 'master', + branch_name: 'master', + commit_message: "Add activerecord.gemspec", + file_path: 'activerecord.gemspec', + file_content: <<-SPEC.strip_heredoc + Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = "activerecord" + end + SPEC + ).execute + + visit_blob('activerecord.gemspec') + end + + it 'displays an auxiliary viewer' do + aggregate_failures do + # shows license + expect(page).to have_content('This project manages its dependencies using RubyGems and defines a gem named activerecord.') + + # shows a link to the gem + expect(page).to have_link('activerecord', 'https://rubygems.org/gems/activerecord') + + # shows a learn more link + expect(page).to have_link('Learn more', 'http://choosealicense.com/licenses/mit/') end end end diff --git a/spec/models/blob_viewer/composer_json_spec.rb b/spec/models/blob_viewer/composer_json_spec.rb new file mode 100644 index 00000000000..df4f1f4815c --- /dev/null +++ b/spec/models/blob_viewer/composer_json_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe BlobViewer::ComposerJson, model: true do + include FakeBlobHelpers + + let(:project) { build(:project) } + let(:data) do + <<-SPEC.strip_heredoc + { + "name": "laravel/laravel", + "homepage": "https://laravel.com/" + } + SPEC + end + let(:blob) { fake_blob(path: 'composer.json', data: data) } + subject { described_class.new(blob) } + + describe '#package_name' do + it 'returns the package name' do + expect(subject).to receive(:prepare!) + + expect(subject.package_name).to eq('laravel/laravel') + end + end +end diff --git a/spec/models/blob_viewer/gemspec_spec.rb b/spec/models/blob_viewer/gemspec_spec.rb new file mode 100644 index 00000000000..81e932de290 --- /dev/null +++ b/spec/models/blob_viewer/gemspec_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe BlobViewer::Gemspec, model: true do + include FakeBlobHelpers + + let(:project) { build(:project) } + let(:data) do + <<-SPEC.strip_heredoc + Gem::Specification.new do |s| + s.platform = Gem::Platform::RUBY + s.name = "activerecord" + end + SPEC + end + let(:blob) { fake_blob(path: 'activerecord.gemspec', data: data) } + subject { described_class.new(blob) } + + describe '#package_name' do + it 'returns the package name' do + expect(subject).to receive(:prepare!) + + expect(subject.package_name).to eq('activerecord') + end + end +end diff --git a/spec/models/blob_viewer/package_json_spec.rb b/spec/models/blob_viewer/package_json_spec.rb new file mode 100644 index 00000000000..5c9a9c81963 --- /dev/null +++ b/spec/models/blob_viewer/package_json_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe BlobViewer::PackageJson, model: true do + include FakeBlobHelpers + + let(:project) { build(:project) } + let(:data) do + <<-SPEC.strip_heredoc + { + "name": "module-name", + "version": "10.3.1" + } + SPEC + end + let(:blob) { fake_blob(path: 'package.json', data: data) } + subject { described_class.new(blob) } + + describe '#package_name' do + it 'returns the package name' do + expect(subject).to receive(:prepare!) + + expect(subject.package_name).to eq('module-name') + end + end +end diff --git a/spec/models/blob_viewer/podspec_json_spec.rb b/spec/models/blob_viewer/podspec_json_spec.rb new file mode 100644 index 00000000000..42a00940bc5 --- /dev/null +++ b/spec/models/blob_viewer/podspec_json_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe BlobViewer::PodspecJson, model: true do + include FakeBlobHelpers + + let(:project) { build(:project) } + let(:data) do + <<-SPEC.strip_heredoc + { + "name": "AFNetworking", + "version": "2.0.0" + } + SPEC + end + let(:blob) { fake_blob(path: 'AFNetworking.podspec.json', data: data) } + subject { described_class.new(blob) } + + describe '#package_name' do + it 'returns the package name' do + expect(subject).to receive(:prepare!) + + expect(subject.package_name).to eq('AFNetworking') + end + end +end diff --git a/spec/models/blob_viewer/podspec_spec.rb b/spec/models/blob_viewer/podspec_spec.rb new file mode 100644 index 00000000000..6c9f0f42d53 --- /dev/null +++ b/spec/models/blob_viewer/podspec_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe BlobViewer::Podspec, model: true do + include FakeBlobHelpers + + let(:project) { build(:project) } + let(:data) do + <<-SPEC.strip_heredoc + Pod::Spec.new do |spec| + spec.name = 'Reachability' + spec.version = '3.1.0' + end + SPEC + end + let(:blob) { fake_blob(path: 'Reachability.podspec', data: data) } + subject { described_class.new(blob) } + + describe '#package_name' do + it 'returns the package name' do + expect(subject).to receive(:prepare!) + + expect(subject.package_name).to eq('Reachability') + end + end +end From 1ede99571b1d65c146757267e8104ecbc026f6ff Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 18 May 2017 09:11:23 -0500 Subject: [PATCH 2/2] Fix inaccurate code comment --- spec/features/projects/blobs/blob_show_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/projects/blobs/blob_show_spec.rb b/spec/features/projects/blobs/blob_show_spec.rb index c95003f3226..fc242082278 100644 --- a/spec/features/projects/blobs/blob_show_spec.rb +++ b/spec/features/projects/blobs/blob_show_spec.rb @@ -452,7 +452,7 @@ feature 'File blob', :js, feature: true do it 'displays an auxiliary viewer' do aggregate_failures do - # shows license + # shows names of dependency manager and package expect(page).to have_content('This project manages its dependencies using RubyGems and defines a gem named activerecord.') # shows a link to the gem