diff --git a/Gemfile b/Gemfile index 199ef65d922d7c64b9d246d2683d6319be54f8e9..ca4157cf47da071b8af1ec4f76b347646bd6ed02 100644 --- a/Gemfile +++ b/Gemfile @@ -190,6 +190,9 @@ gem 'babosa', '~> 1.0.2' # Sanitizes SVG input gem "loofah", "~> 2.0.3" +# Working with license +gem 'licensee', "~> 7.0.0" + # Protect against bruteforcing gem "rack-attack", '~> 4.3.1' diff --git a/Gemfile.lock b/Gemfile.lock index ad7d7c18559538c18607bc6c7070c195b4864927..2a6faeff2f45bc65a7c6ba993b98fcefdc9fac41 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -452,6 +452,8 @@ GEM addressable (~> 2.3) letter_opener (1.1.2) launchy (~> 2.2) + licensee (7.0.0) + rugged (~> 0.23) listen (3.0.5) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) @@ -957,6 +959,7 @@ DEPENDENCIES jquery-ui-rails (~> 5.0.0) kaminari (~> 0.16.3) letter_opener (~> 1.1.2) + licensee (~> 7.0.0) loofah (~> 2.0.3) mail_room (~> 0.6.1) method_source (~> 0.8) diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index f3ed9a66715fe40588441ec74caadb2fcb52738a..f4757658d54463d7e8762586a417cb7717536c99 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -5,6 +5,7 @@ group_projects_path: "/api/:version/groups/:id/projects.json" projects_path: "/api/:version/projects.json" labels_path: "/api/:version/projects/:id/labels" + license_path: "/api/:version/licenses/:key" group: (group_id, callback) -> url = Api.buildUrl(Api.group_path) @@ -92,6 +93,18 @@ ).done (projects) -> callback(projects) + # Return text for specific license + licenseText: (key, fullname, callback) -> + url = Api.buildUrl(Api.license_path) + url = url.replace(':key', key) + + $.ajax( + url: url + data: + fullname: fullname + ).done (projects) -> + callback(projects) + buildUrl: (url) -> url = gon.relative_url_root + url if gon.relative_url_root? return url.replace(':version', gon.api_version) diff --git a/app/assets/javascripts/blob/blob_license_selector.js.coffee b/app/assets/javascripts/blob/blob_license_selector.js.coffee new file mode 100644 index 0000000000000000000000000000000000000000..b43c1ec2bb5834d606b0ea5859b6c457dfe01cb4 --- /dev/null +++ b/app/assets/javascripts/blob/blob_license_selector.js.coffee @@ -0,0 +1,24 @@ +class @BlobLicenseSelector + licenseRegex: /^(.+\/)?(licen[sc]e|copying)($|\.)/i + + constructor: (editor)-> + self = this + @licenseSelector = $('.js-license-selector') + @toggleLicenseSelector($('#file_name').val()) + + $('#file_name').on 'input', -> + self.toggleLicenseSelector($(this).val()) + + $('select.license-select').select2( + width: 'resolve' + dropdownAutoWidth: true + placeholder: 'Choose a license template' + ).on 'change', (e) -> + Api.licenseText $(this).val(), $(this).data('fullname'), (data) -> + editor.setValue(data, -1) + + toggleLicenseSelector: (fileName) => + if @licenseRegex.test(fileName) + @licenseSelector.show() + else + @licenseSelector.hide() diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index 0f0592a0ab8bcc57416619b7e654319bcab11945..8981f070a20337d9184f73b2a4d37c4cbd6015ee 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -26,6 +26,10 @@ line-height: 42px; padding-top: 7px; padding-bottom: 7px; + + .pull-right { + height: 20px; + } } .editor-ref { @@ -53,4 +57,9 @@ .select2 { float: right; } + + .encoding-selector, + .license-selector { + display: inline-block; + } } diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index cd8b2911674b78cac69bdee0835f5da98e0f5a22..6960562d15ff1981647072253b27a8d379abb4e1 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -122,6 +122,15 @@ class Projects::BlobController < Projects::ApplicationController end def editor_variables + @licenses = { + 'Popular' => Licensee::License.all(featured: true).map!{ |license| [license.name, license.key] }, + 'Other' => Licensee::License.all(featured: false).map!{ |license| [license.name, license.key] } + } + + unless @repository.empty? + @current_license_key = Licensee.license(@repository.path).try(:key) + end + @target_branch = params[:target_branch] @file_path = diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 7e00aacceaaa1ef639bd0143cdff624c826f3c38..0b0cb658b1781cc84f3bc00a8b24dfdaaee1252a 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -306,6 +306,13 @@ module ProjectsHelper namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md') end + def new_license_path + ref = @repository.root_ref if @repository + ref ||= 'master' + + namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'LICENSE') + end + def last_push_event if current_user current_user.recent_push(@project.id) diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index f8b6fa253c4ff9ddfa6714976bfae25dbb9ba867..8ebbc91f81d2b68c1d48b70709caf50261605da9 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -13,7 +13,11 @@ required: true, class: 'form-control new-file-name' .pull-right - = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' + .license-selector.js-license-selector.hide + = select_tag :license_type, grouped_options_for_select(@licenses, @current_license_key), include_blank: true, class: 'select2 license-select', data: { fullname: @repository.project.creator.name } + + .encoding-selector + = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'select2' .file-content.code %pre.js-edit-mode-pane#editor #{params[:content] || local_assigns[:blob_data]} @@ -22,3 +26,8 @@ .center %h2 %i.icon-spinner.icon-spin + +:javascript + window.onload = function() { + new BlobLicenseSelector(blob.editor) + } diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 6ad7b05155a32b2dc1297fea821e6f5d1bf96678..2eb79703304d8c95a6165dbe2c5e869b4835ac24 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -14,8 +14,10 @@ %p If you already have files you can push them using command line instructions below. %p - Otherwise you can start with - = link_to "adding README", new_readme_path, class: 'underlined-link' + Otherwise you can start with adding + = link_to "README", new_readme_path, class: 'underlined-link' + or + = link_to "LICENSE", new_license_path, class: 'underlined-link' file to this project. - if can?(current_user, :push_code, @project) diff --git a/lib/api/api.rb b/lib/api/api.rb index 7d65145176b9059eb2ae12fcd76a9613bcd37dac..cc1004f80059e85ca310aa0ef55e73fe778f274b 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -57,5 +57,6 @@ module API mount Builds mount Variables mount Runners + mount Licenses end end diff --git a/lib/api/licenses.rb b/lib/api/licenses.rb new file mode 100644 index 0000000000000000000000000000000000000000..7fffbef84e4dc9763dbfb82f86405c7e5695a1f6 --- /dev/null +++ b/lib/api/licenses.rb @@ -0,0 +1,27 @@ +module API + # Licenses API + class Licenses < Grape::API + YEAR_TEMPLATE_REGEX = /(\[|<|{)(year|yyyy)(\]|>|})/ + FULLNAME_TEMPLATE_REGEX = /\[fullname\]/ + + # Get text for specific license + # + # Parameters: + # key (required) - The key of a license + # fullname - Reository owner fullname + # Example Request: + # GET /licenses/mit + get 'licenses/:key', requirements: { key: /[\w.-]*/ } do + env['api.format'] = :txt + license = Licensee::License.find(params[:key]).try(:text) + + if license + license + .gsub(YEAR_TEMPLATE_REGEX, Time.now.year.to_s) + .gsub(FULLNAME_TEMPLATE_REGEX, params[:fullname]) + else + error!('License not found', 404) + end + end + end +end diff --git a/spec/requests/api/licenses_spec.rb b/spec/requests/api/licenses_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..f0f8b041f6982d148236ed5fe210ad47fc146002 --- /dev/null +++ b/spec/requests/api/licenses_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + describe 'GET /licenses/:key' do + before(:each) do + get api("/licenses/#{license_type}?fullname=Anton") + end + + context 'for mit license name' do + let(:license_type){ 'mit' } + + it 'returns MIT license text and replases template values' do + expect(response.body).to include('Copyright (c) 2016 Anton') + expect(response.body).to include('Copyright (c) 2016') + end + end + + context 'for gnu license name' do + let(:license_type){ 'gpl-3.0' } + + it 'returns GNU license text and replases template values' do + expect(response.body).to include('GNU GENERAL PUBLIC LICENSE') + expect(response.body).to include('Copyright (C) 2016') + end + end + + context 'for apache license name' do + let(:license_type){ 'apache-2.0' } + + it 'returns Apache license text and replases template values' do + expect(response.body).to include('Apache License') + expect(response.body).to include('Copyright 2016') + end + end + + context 'for mythic license name' do + let(:license_type){ 'muth-over9000' } + + it 'returns string with error' do + expect(response).to have_http_status(404) + expect(response.body).to eq 'License not found' + end + end + end +end