From e179707844b043fac6f81a30f82e5f4ba446abde Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sun, 10 Jul 2016 15:10:59 -0500 Subject: [PATCH 1/8] Extract generic parts of Gitlab::Diff::InlineDiffMarker --- app/services/system_note_service.rb | 4 +- .../diff/inline_diff_markdown_marker.rb | 17 +++ lib/gitlab/diff/inline_diff_marker.rb | 130 +----------------- lib/gitlab/string_range_marker.rb | 102 ++++++++++++++ .../diff/inline_diff_markdown_marker_spec.rb | 14 ++ .../gitlab/diff/inline_diff_marker_spec.rb | 18 +-- spec/lib/gitlab/string_range_marker_spec.rb | 36 +++++ 7 files changed, 187 insertions(+), 134 deletions(-) create mode 100644 lib/gitlab/diff/inline_diff_markdown_marker.rb create mode 100644 lib/gitlab/string_range_marker.rb create mode 100644 spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb create mode 100644 spec/lib/gitlab/string_range_marker_spec.rb diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 174e7c6e95b..cb93def07df 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -291,8 +291,8 @@ module SystemNoteService old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_title, new_title).inline_diffs - marked_old_title = Gitlab::Diff::InlineDiffMarker.new(old_title).mark(old_diffs, mode: :deletion, markdown: true) - marked_new_title = Gitlab::Diff::InlineDiffMarker.new(new_title).mark(new_diffs, mode: :addition, markdown: true) + marked_old_title = Gitlab::Diff::InlineDiffMarkdownMarker.new(old_title).mark(old_diffs, mode: :deletion) + marked_new_title = Gitlab::Diff::InlineDiffMarkdownMarker.new(new_title).mark(new_diffs, mode: :addition) body = "changed title from **#{marked_old_title}** to **#{marked_new_title}**" diff --git a/lib/gitlab/diff/inline_diff_markdown_marker.rb b/lib/gitlab/diff/inline_diff_markdown_marker.rb new file mode 100644 index 00000000000..c2a2eb15931 --- /dev/null +++ b/lib/gitlab/diff/inline_diff_markdown_marker.rb @@ -0,0 +1,17 @@ +module Gitlab + module Diff + class InlineDiffMarkdownMarker < Gitlab::StringRangeMarker + MARKDOWN_SYMBOLS = { + addition: "+", + deletion: "-" + }.freeze + + def mark(line_inline_diffs, mode: nil) + super(line_inline_diffs) do |text, left:, right:| + symbol = MARKDOWN_SYMBOLS[mode] + "{#{symbol}#{text}#{symbol}}" + end + end + end + end +end diff --git a/lib/gitlab/diff/inline_diff_marker.rb b/lib/gitlab/diff/inline_diff_marker.rb index 736933b1c4b..919965100ae 100644 --- a/lib/gitlab/diff/inline_diff_marker.rb +++ b/lib/gitlab/diff/inline_diff_marker.rb @@ -1,137 +1,21 @@ module Gitlab module Diff - class InlineDiffMarker - MARKDOWN_SYMBOLS = { - addition: "+", - deletion: "-" - }.freeze - - attr_accessor :raw_line, :rich_line - - def initialize(raw_line, rich_line = raw_line) - @raw_line = raw_line - @rich_line = ERB::Util.html_escape(rich_line) - end - - def mark(line_inline_diffs, mode: nil, markdown: false) - return rich_line unless line_inline_diffs - - marker_ranges = [] - line_inline_diffs.each do |inline_diff_range| - # Map the inline-diff range based on the raw line to character positions in the rich line - inline_diff_positions = position_mapping[inline_diff_range].flatten - # Turn the array of character positions into ranges - marker_ranges.concat(collapse_ranges(inline_diff_positions)) + class InlineDiffMarker < Gitlab::StringRangeMarker + def mark(line_inline_diffs, mode: nil) + super(line_inline_diffs) do |text, left:, right:| + %{#{text}} end - - offset = 0 - - # Mark each range - marker_ranges.each_with_index do |range, index| - before_content = - if markdown - "{#{MARKDOWN_SYMBOLS[mode]}" - else - "" - end - after_content = - if markdown - "#{MARKDOWN_SYMBOLS[mode]}}" - else - "" - end - offset = insert_around_range(rich_line, range, before_content, after_content, offset) - end - - rich_line.html_safe end private - def html_class_names(marker_ranges, mode, index) + def html_class_names(left, right, mode) class_names = ["idiff"] - class_names << "left" if index == 0 - class_names << "right" if index == marker_ranges.length - 1 + class_names << "left" if left + class_names << "right" if right class_names << mode if mode class_names.join(" ") end - - # Mapping of character positions in the raw line, to the rich (highlighted) line - def position_mapping - @position_mapping ||= begin - mapping = [] - rich_pos = 0 - (0..raw_line.length).each do |raw_pos| - rich_char = rich_line[rich_pos] - - # The raw and rich lines are the same except for HTML tags, - # so skip over any `<...>` segment - while rich_char == '<' - until rich_char == '>' - rich_pos += 1 - rich_char = rich_line[rich_pos] - end - - rich_pos += 1 - rich_char = rich_line[rich_pos] - end - - # multi-char HTML entities in the rich line correspond to a single character in the raw line - if rich_char == '&' - multichar_mapping = [rich_pos] - until rich_char == ';' - rich_pos += 1 - multichar_mapping << rich_pos - rich_char = rich_line[rich_pos] - end - - mapping[raw_pos] = multichar_mapping - else - mapping[raw_pos] = rich_pos - end - - rich_pos += 1 - end - - mapping - end - end - - # Takes an array of integers, and returns an array of ranges covering the same integers - def collapse_ranges(positions) - return [] if positions.empty? - ranges = [] - - start = prev = positions[0] - range = start..prev - positions[1..-1].each do |pos| - if pos == prev + 1 - range = start..pos - prev = pos - else - ranges << range - start = prev = pos - range = start..prev - end - end - ranges << range - - ranges - end - - # Inserts tags around the characters identified by the given range - def insert_around_range(text, range, before, after, offset = 0) - # Just to be sure - return offset if offset + range.end + 1 > text.length - - text.insert(offset + range.begin, before) - offset += before.length - - text.insert(offset + range.end + 1, after) - offset += after.length - - offset - end end end end diff --git a/lib/gitlab/string_range_marker.rb b/lib/gitlab/string_range_marker.rb new file mode 100644 index 00000000000..94fba0a221a --- /dev/null +++ b/lib/gitlab/string_range_marker.rb @@ -0,0 +1,102 @@ +module Gitlab + class StringRangeMarker + attr_accessor :raw_line, :rich_line + + def initialize(raw_line, rich_line = raw_line) + @raw_line = raw_line + @rich_line = ERB::Util.html_escape(rich_line) + end + + def mark(marker_ranges) + return rich_line unless marker_ranges + + rich_marker_ranges = [] + marker_ranges.each do |range| + # Map the inline-diff range based on the raw line to character positions in the rich line + rich_positions = position_mapping[range].flatten + # Turn the array of character positions into ranges + rich_marker_ranges.concat(collapse_ranges(rich_positions)) + end + + offset = 0 + # Mark each range + rich_marker_ranges.each_with_index do |range, i| + offset_range = (range.begin + offset)..(range.end + offset) + original_text = rich_line[offset_range] + + text = yield(original_text, left: i == 0, right: i == rich_marker_ranges.length - 1) + + rich_line[offset_range] = text + + offset += text.length - original_text.length + end + + rich_line.html_safe + end + + private + + # Mapping of character positions in the raw line, to the rich (highlighted) line + def position_mapping + @position_mapping ||= begin + mapping = [] + rich_pos = 0 + (0..raw_line.length).each do |raw_pos| + rich_char = rich_line[rich_pos] + + # The raw and rich lines are the same except for HTML tags, + # so skip over any `<...>` segment + while rich_char == '<' + until rich_char == '>' + rich_pos += 1 + rich_char = rich_line[rich_pos] + end + + rich_pos += 1 + rich_char = rich_line[rich_pos] + end + + # multi-char HTML entities in the rich line correspond to a single character in the raw line + if rich_char == '&' + multichar_mapping = [rich_pos] + until rich_char == ';' + rich_pos += 1 + multichar_mapping << rich_pos + rich_char = rich_line[rich_pos] + end + + mapping[raw_pos] = multichar_mapping + else + mapping[raw_pos] = rich_pos + end + + rich_pos += 1 + end + + mapping + end + end + + # Takes an array of integers, and returns an array of ranges covering the same integers + def collapse_ranges(positions) + return [] if positions.empty? + ranges = [] + + start = prev = positions[0] + range = start..prev + positions[1..-1].each do |pos| + if pos == prev + 1 + range = start..pos + prev = pos + else + ranges << range + start = prev = pos + range = start..prev + end + end + ranges << range + + ranges + end + end +end diff --git a/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb new file mode 100644 index 00000000000..d6e8b8ac4b2 --- /dev/null +++ b/spec/lib/gitlab/diff/inline_diff_markdown_marker_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe Gitlab::Diff::InlineDiffMarkdownMarker, lib: true do + describe '#mark' do + let(:raw) { "abc 'def'" } + let(:inline_diffs) { [2..5] } + let(:subject) { described_class.new(raw).mark(inline_diffs, mode: :deletion) } + + it 'marks the range' do + expect(subject).to eq("ab{-c 'd-}ef'") + expect(subject).to be_html_safe + end + end +end diff --git a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb index 198ff977f24..95da344802d 100644 --- a/spec/lib/gitlab/diff/inline_diff_marker_spec.rb +++ b/spec/lib/gitlab/diff/inline_diff_marker_spec.rb @@ -1,26 +1,26 @@ require 'spec_helper' describe Gitlab::Diff::InlineDiffMarker, lib: true do - describe '#inline_diffs' do + describe '#mark' do context "when the rich text is html safe" do - let(:raw) { "abc 'def'" } + let(:raw) { "abc 'def'" } let(:rich) { %{abc 'def'}.html_safe } let(:inline_diffs) { [2..5] } - let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw, rich).mark(inline_diffs) } + let(:subject) { described_class.new(raw, rich).mark(inline_diffs) } - it 'marks the inline diffs' do - expect(subject).to eq(%{abc 'def'}) + it 'marks the range' do + expect(subject).to eq(%{abc 'def'}) expect(subject).to be_html_safe end end context "when the text text is not html safe" do - let(:raw) { "abc 'def'" } + let(:raw) { "abc 'def'" } let(:inline_diffs) { [2..5] } - let(:subject) { Gitlab::Diff::InlineDiffMarker.new(raw).mark(inline_diffs) } + let(:subject) { described_class.new(raw).mark(inline_diffs) } - it 'marks the inline diffs' do - expect(subject).to eq(%{abc 'def'}) + it 'marks the range' do + expect(subject).to eq(%{abc 'def'}) expect(subject).to be_html_safe end end diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb new file mode 100644 index 00000000000..45b4d4af206 --- /dev/null +++ b/spec/lib/gitlab/string_range_marker_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Gitlab::StringRangeMarker, lib: true do + describe '#mark' do + context "when the rich text is html safe" do + let(:raw) { "abc " } + let(:rich) { %{abc <def>}.html_safe } + let(:inline_diffs) { [2..5] } + let(:subject) do + described_class.new(raw, rich).mark(inline_diffs) do |text, left:, right:| + "LEFT#{text}RIGHT" + end + end + + it 'marks the inline diffs' do + expect(subject).to eq(%{abLEFTcRIGHTLEFT RIGHTLEFT<dRIGHTef>}) + expect(subject).to be_html_safe + end + end + + context "when the rich text is not html safe" do + let(:raw) { "abc " } + let(:inline_diffs) { [2..5] } + let(:subject) do + described_class.new(raw).mark(inline_diffs) do |text, left:, right:| + "LEFT#{text}RIGHT" + end + end + + it 'marks the inline diffs' do + expect(subject).to eq(%{abLEFTc <dRIGHTef>}) + expect(subject).to be_html_safe + end + end + end +end From 3f6d91c53c1bf99ce2e7dfeb9c25ef5f8149b72e Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sun, 10 Jul 2016 15:17:20 -0500 Subject: [PATCH 2/8] Add Gitlab::StringRegexMarker --- lib/gitlab/string_regex_marker.rb | 13 +++++++++++++ spec/lib/gitlab/string_range_marker_spec.rb | 4 ++-- spec/lib/gitlab/string_regex_marker_spec.rb | 18 ++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 lib/gitlab/string_regex_marker.rb create mode 100644 spec/lib/gitlab/string_regex_marker_spec.rb diff --git a/lib/gitlab/string_regex_marker.rb b/lib/gitlab/string_regex_marker.rb new file mode 100644 index 00000000000..7ebf1c0428c --- /dev/null +++ b/lib/gitlab/string_regex_marker.rb @@ -0,0 +1,13 @@ +module Gitlab + class StringRegexMarker < StringRangeMarker + def mark(regex, group: 0, &block) + regex_match = raw_line.match(regex) + return rich_line unless regex_match + + begin_index, end_index = regex_match.offset(group) + name_range = begin_index..(end_index - 1) + + super([name_range], &block) + end + end +end diff --git a/spec/lib/gitlab/string_range_marker_spec.rb b/spec/lib/gitlab/string_range_marker_spec.rb index 45b4d4af206..7c77772b3f6 100644 --- a/spec/lib/gitlab/string_range_marker_spec.rb +++ b/spec/lib/gitlab/string_range_marker_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::StringRangeMarker, lib: true do let(:raw) { "abc " } let(:rich) { %{abc <def>}.html_safe } let(:inline_diffs) { [2..5] } - let(:subject) do + subject do described_class.new(raw, rich).mark(inline_diffs) do |text, left:, right:| "LEFT#{text}RIGHT" end @@ -21,7 +21,7 @@ describe Gitlab::StringRangeMarker, lib: true do context "when the rich text is not html safe" do let(:raw) { "abc " } let(:inline_diffs) { [2..5] } - let(:subject) do + subject do described_class.new(raw).mark(inline_diffs) do |text, left:, right:| "LEFT#{text}RIGHT" end diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb new file mode 100644 index 00000000000..2f5cf6c6e3b --- /dev/null +++ b/spec/lib/gitlab/string_regex_marker_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe Gitlab::StringRegexMarker, lib: true do + describe '#mark' do + let(:raw) { %{"name": "AFNetworking"} } + let(:rich) { %{"name": "AFNetworking"}.html_safe } + subject do + described_class.new(raw, rich).mark(/"[^"]+":\s*"(?[^"]+)"/, group: :name) do |text, left:, right:| + %{#{text}} + end + end + + it 'marks the inline diffs' do + expect(subject).to eq(%{"name": "AFNetworking"}) + expect(subject).to be_html_safe + end + end +end From b1012d986c4142d7c45d46fa70511ea5faa6150f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sun, 10 Jul 2016 16:13:06 -0500 Subject: [PATCH 3/8] Autolink package names in Gemfile --- lib/gitlab/dependency_linker.rb | 18 +++ lib/gitlab/dependency_linker/base_linker.rb | 103 ++++++++++++++++++ .../dependency_linker/gemfile_linker.rb | 28 +++++ lib/gitlab/highlight.rb | 37 +++++-- .../dependency_linker/gemfile_linker_spec.rb | 63 +++++++++++ spec/lib/gitlab/dependency_linker_spec.rb | 13 +++ spec/lib/gitlab/highlight_spec.rb | 11 ++ 7 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 lib/gitlab/dependency_linker.rb create mode 100644 lib/gitlab/dependency_linker/base_linker.rb create mode 100644 lib/gitlab/dependency_linker/gemfile_linker.rb create mode 100644 spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb create mode 100644 spec/lib/gitlab/dependency_linker_spec.rb diff --git a/lib/gitlab/dependency_linker.rb b/lib/gitlab/dependency_linker.rb new file mode 100644 index 00000000000..5e884c4bea1 --- /dev/null +++ b/lib/gitlab/dependency_linker.rb @@ -0,0 +1,18 @@ +module Gitlab + module DependencyLinker + LINKERS = [ + GemfileLinker, + ].freeze + + def self.linker(blob_name) + LINKERS.find { |linker| linker.support?(blob_name) } + end + + def self.link(blob_name, plain_text, highlighted_text) + linker = linker(blob_name) + return highlighted_text unless linker + + linker.link(plain_text, highlighted_text) + end + end +end diff --git a/lib/gitlab/dependency_linker/base_linker.rb b/lib/gitlab/dependency_linker/base_linker.rb new file mode 100644 index 00000000000..40a4ad11372 --- /dev/null +++ b/lib/gitlab/dependency_linker/base_linker.rb @@ -0,0 +1,103 @@ +module Gitlab + module DependencyLinker + class BaseLinker + def self.link(plain_text, highlighted_text) + new(plain_text, highlighted_text).link + end + + attr_accessor :plain_text, :highlighted_text + + def initialize(plain_text, highlighted_text) + @plain_text = plain_text + @highlighted_text = highlighted_text + end + + def link + link_dependencies + + highlighted_lines.join.html_safe + end + + private + + def package_url(name) + raise NotImplementedError + end + + def link_dependencies + raise NotImplementedError + end + + def package_link(name, url = package_url(name)) + return name unless url + + %{#{ERB::Util.html_escape_once(name)}} + end + + # Links package names in a method call or assignment string argument. + # + # Example: + # link_method_call("gem") + # # Will link `package` in `gem "package"`, `gem("package")` and `gem = "package"` + # + # link_method_call("gem", "specific_package") + # # Will link `specific_package` in `gem "specific_package"` + # + # link_method_call("github", /[^\/]+\/[^\/]+/) + # # Will link `user/repo` in `github "user/repo"`, but not `github "package"` + # + # link_method_call(%w[add_dependency add_development_dependency]) + # # Will link `spec.add_dependency "package"` and `spec.add_development_dependency "package"` + # + # link_method_call("name") + # # Will link `package` in `self.name = "package"` + def link_method_call(method_names, value = nil, &url_proc) + value = + case value + when String + Regexp.escape(value) + when nil + /[^'"]+/ + else + value + end + + method_names = Array(method_names).map { |name| Regexp.escape(name) } + + regex = %r{ + #{Regexp.union(method_names)} # Method name + \s* # Whitespace + [(=]? # Opening brace or equals sign + \s* # Whitespace + ['"](?#{value})['"] # Package name in quotes + }x + + link_regex(regex, &url_proc) + end + + # Links package names based on regex. + # + # Example: + # link_regex(/(github:|:github =>)\s*['"](?[^'"]+)['"]/) + # # Will link `user/repo` in `github: "user/repo"` or `:github => "user/repo"` + def link_regex(regex) + highlighted_lines.map!.with_index do |rich_line, i| + marker = StringRegexMarker.new(plain_lines[i], rich_line.html_safe) + + marker.mark(regex, group: :name) do |text, left:, right:| + url = block_given? ? yield(text) : package_url(text) + package_link(text, url) + end + end + end + + def plain_lines + @plain_lines ||= plain_text.lines + end + + def highlighted_lines + @highlighted_lines ||= highlighted_text.lines + end + end + end +end diff --git a/lib/gitlab/dependency_linker/gemfile_linker.rb b/lib/gitlab/dependency_linker/gemfile_linker.rb new file mode 100644 index 00000000000..e36b88d357d --- /dev/null +++ b/lib/gitlab/dependency_linker/gemfile_linker.rb @@ -0,0 +1,28 @@ +module Gitlab + module DependencyLinker + class GemfileLinker < BaseLinker + def self.support?(blob_name) + blob_name == 'Gemfile' || blob_name == 'gems.rb' + end + + private + + def link_dependencies + # Link `gem "package_name"` to https://rubygems.org/gems/package_name + link_method_call("gem") + + # Link `github: "user/repo"` to https://github.com/user/repo + link_regex(/(github:|:github\s*=>)\s*['"](?[^'"]+)['"]/) do |name| + "https://github.com/#{name}" + end + + # Link `source "https://rubygems.org"` to https://rubygems.org + link_method_call("source", %r{https?://[^'"]+}) { |url| url } + end + + def package_url(name) + "https://rubygems.org/gems/#{name}" + end + end + end +end diff --git a/lib/gitlab/highlight.rb b/lib/gitlab/highlight.rb index d787d5db4a0..83bc230df3e 100644 --- a/lib/gitlab/highlight.rb +++ b/lib/gitlab/highlight.rb @@ -13,6 +13,8 @@ module Gitlab highlight(file_name, blob.data, repository: repository).lines.map!(&:html_safe) end + attr_reader :blob_name + def initialize(blob_name, blob_content, repository: nil) @formatter = Rouge::Formatters::HTMLGitlab @repository = repository @@ -21,16 +23,9 @@ module Gitlab end def highlight(text, continue: true, plain: false) - if plain - hl_lexer = Rouge::Lexers::PlainText - continue = false - else - hl_lexer = self.lexer - end - - @formatter.format(hl_lexer.lex(text, continue: continue), tag: hl_lexer.tag).html_safe - rescue - @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe + highlighted_text = highlight_text(text, continue: continue, plain: plain) + highlighted_text = link_dependencies(text, highlighted_text) if blob_name + highlighted_text end def lexer @@ -50,5 +45,27 @@ module Gitlab Rouge::Lexer.find_fancy(language_name) end + + def highlight_text(text, continue: true, plain: false) + if plain + highlight_plain(text) + else + highlight_rich(text, continue: continue) + end + end + + def highlight_plain(text) + @formatter.format(Rouge::Lexers::PlainText.lex(text)).html_safe + end + + def highlight_rich(text, continue: true) + @formatter.format(lexer.lex(text, continue: continue), tag: lexer.tag).html_safe + rescue + highlight_plain(text) + end + + def link_dependencies(text, highlighted_text) + Gitlab::DependencyLinker.link(blob_name, text, highlighted_text) + end end end diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb new file mode 100644 index 00000000000..62e477e1de0 --- /dev/null +++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb @@ -0,0 +1,63 @@ +require 'rails_helper' + +describe Gitlab::DependencyLinker::GemfileLinker, lib: true do + describe '.support?' do + it 'supports Gemfile' do + expect(described_class.support?('Gemfile')).to be_truthy + end + + it 'supports gems.rb' do + expect(described_class.support?('gems.rb')).to be_truthy + end + + it 'does not support other files' do + expect(described_class.support?('Gemfile.lock')).to be_falsey + end + end + + describe '#link' do + let(:file_name) { 'Gemfile' } + + let(:file_content) do + <<-CONTENT.strip_heredoc + source 'https://rubygems.org' + + gem "rails", '4.2.6', github: "rails/rails" + gem 'rails-deprecated_sanitizer', '~> 1.0.3' + + # Responders respond_to and respond_with + gem 'responders', '~> 2.0', :github => 'rails/responders' + + # Specify a sprockets version due to increased performance + # See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069 + gem 'sprockets', '~> 3.6.0' + + # Default values for AR models + gem 'default_value_for', '~> 3.0.0' + CONTENT + end + + subject { Gitlab::Highlight.highlight(file_name, file_content) } + + def link(name, url) + %{#{name}} + end + + it 'links sources' do + expect(subject).to include(link('https://rubygems.org', 'https://rubygems.org')) + end + + it 'links dependencies' do + expect(subject).to include(link('rails', 'https://rubygems.org/gems/rails')) + expect(subject).to include(link('rails-deprecated_sanitizer', 'https://rubygems.org/gems/rails-deprecated_sanitizer')) + expect(subject).to include(link('responders', 'https://rubygems.org/gems/responders')) + expect(subject).to include(link('sprockets', 'https://rubygems.org/gems/sprockets')) + expect(subject).to include(link('default_value_for', 'https://rubygems.org/gems/default_value_for')) + end + + it 'links GitHub repos' do + expect(subject).to include(link('rails/rails', 'https://github.com/rails/rails')) + expect(subject).to include(link('rails/responders', 'https://github.com/rails/responders')) + end + end +end diff --git a/spec/lib/gitlab/dependency_linker_spec.rb b/spec/lib/gitlab/dependency_linker_spec.rb new file mode 100644 index 00000000000..03d5b61d70c --- /dev/null +++ b/spec/lib/gitlab/dependency_linker_spec.rb @@ -0,0 +1,13 @@ +require 'rails_helper' + +describe Gitlab::DependencyLinker, lib: true do + describe '.link' do + it 'links using GemfileLinker' do + blob_name = 'Gemfile' + + expect(described_class::GemfileLinker).to receive(:link) + + described_class.link(blob_name, nil, nil) + end + end +end diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index e49799ad105..e57b3053871 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -57,4 +57,15 @@ describe Gitlab::Highlight, lib: true do end end end + + describe '#highlight' do + subject { described_class.highlight(file_name, file_content, nowrap: false) } + + it 'links dependencies via DependencyLinker' do + expect(Gitlab::DependencyLinker).to receive(:link). + with('file.name', 'Contents', anything).and_call_original + + described_class.highlight('file.name', 'Contents') + end + end end From 9ce9e0d3661bb51d05e40db56a891380a96ef90b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 9 May 2017 16:39:10 -0500 Subject: [PATCH 4/8] Turn blob dependency links blue --- app/assets/stylesheets/highlight/dark.scss | 5 +++++ app/assets/stylesheets/highlight/monokai.scss | 5 +++++ app/assets/stylesheets/highlight/solarized_dark.scss | 5 +++++ app/assets/stylesheets/highlight/solarized_light.scss | 5 +++++ app/assets/stylesheets/highlight/white.scss | 5 +++++ 5 files changed, 25 insertions(+) diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index 09951fe3d3e..6e3829d994f 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -185,6 +185,11 @@ $dark-il: #de935f; color: $dark-highlight-color !important; } + // Links to URLs, emails, or dependencies + .line a { + color: $dark-na; + } + .hll { background-color: $dark-hll-bg; } .c { color: $dark-c; } /* Comment */ .err { color: $dark-err; } /* Error */ diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index b6a6d298adf..68eb0c7720f 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -185,6 +185,11 @@ $monokai-gi: #a6e22e; color: $black !important; } + // Links to URLs, emails, or dependencies + .line a { + color: $monokai-k; + } + .hll { background-color: $monokai-hll; } .c { color: $monokai-c; } /* Comment */ .err { color: $monokai-err-color; background-color: $monokai-err-bg; } /* Error */ diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index 4f7a50dcb4f..2cc968c32f2 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -188,6 +188,11 @@ $solarized-dark-il: #2aa198; background-color: $solarized-dark-highlight !important; } + // Links to URLs, emails, or dependencies + .line a { + color: $solarized-dark-kd; + } + /* Solarized Dark For use with Jekyll and Pygments diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss index 6463fe96c1b..b61b85a2cd1 100644 --- a/app/assets/stylesheets/highlight/solarized_light.scss +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -196,6 +196,11 @@ $solarized-light-il: #2aa198; background-color: $solarized-light-highlight !important; } + // Links to URLs, emails, or dependencies + .line a { + color: $solarized-light-kd; + } + /* Solarized Light For use with Jekyll and Pygments diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index ab2018bfbca..1daa10aef24 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -203,6 +203,11 @@ $white-gc-bg: #eaf2f5; background-color: $white-highlight !important; } + // Links to URLs, emails, or dependencies + .line a { + color: $white-nb; + } + .hll { background-color: $white-hll-bg; } .c { color: $white-c; font-style: italic; } .err { color: $white-err; background-color: $white-err-bg; } From 02650d97e5e3134c33514334f4721f5b4d1ebd26 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 9 May 2017 21:46:02 -0500 Subject: [PATCH 5/8] Fix specs --- spec/helpers/diff_helper_spec.rb | 4 ++-- spec/lib/gitlab/diff/highlight_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index eae097126ce..dd6566d25bb 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -122,9 +122,9 @@ describe DiffHelper do it "returns strings with marked inline diffs" do marked_old_line, marked_new_line = mark_inline_diffs(old_line, new_line) - expect(marked_old_line).to eq("abc 'def'") + expect(marked_old_line).to eq(%q{abc 'def'}) expect(marked_old_line).to be_html_safe - expect(marked_new_line).to eq("abc "def"") + expect(marked_new_line).to eq(%q{abc "def"}) expect(marked_new_line).to be_html_safe end end diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index c6bd4e81f4f..7d7d4a55e63 100644 --- a/spec/lib/gitlab/diff/highlight_spec.rb +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -34,7 +34,7 @@ describe Gitlab::Diff::Highlight, lib: true do end it 'highlights and marks added lines' do - code = %Q{+ raise RuntimeError, "System commands must be given as an array of strings"\n} + code = %Q{+ raise RuntimeError, "System commands must be given as an array of strings"\n} expect(subject[5].text).to eq(code) end @@ -67,7 +67,7 @@ describe Gitlab::Diff::Highlight, lib: true do end it 'marks added lines' do - code = %q{+ raise RuntimeError, "System commands must be given as an array of strings"} + code = %q{+ raise RuntimeError, "System commands must be given as an array of strings"} expect(subject[5].text).to eq(code) expect(subject[5].text).to be_html_safe From e9d7b6cd8facc4828fc4abfb82902d803d523882 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 12 May 2017 10:29:25 -0500 Subject: [PATCH 6/8] Link Gemfile git URLs --- lib/gitlab/dependency_linker/gemfile_linker.rb | 3 +++ spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/dependency_linker/gemfile_linker.rb b/lib/gitlab/dependency_linker/gemfile_linker.rb index e36b88d357d..45be760d89e 100644 --- a/lib/gitlab/dependency_linker/gemfile_linker.rb +++ b/lib/gitlab/dependency_linker/gemfile_linker.rb @@ -16,6 +16,9 @@ module Gitlab "https://github.com/#{name}" end + # Link `git: "https://gitlab.example.com/user/repo"` to https://gitlab.example.com/user/repo + link_regex(%r{(git:|:git\s*=>)\s*['"](?https?://[^'"]+)['"]}) { |url| url } + # Link `source "https://rubygems.org"` to https://rubygems.org link_method_call("source", %r{https?://[^'"]+}) { |url| url } end diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb index 62e477e1de0..024a03a0ef5 100644 --- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb @@ -30,7 +30,7 @@ describe Gitlab::DependencyLinker::GemfileLinker, lib: true do # Specify a sprockets version due to increased performance # See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069 - gem 'sprockets', '~> 3.6.0' + gem 'sprockets', '~> 3.6.0', git: 'https://gitlab.example.com/gems/sprockets' # Default values for AR models gem 'default_value_for', '~> 3.0.0' @@ -59,5 +59,9 @@ describe Gitlab::DependencyLinker::GemfileLinker, lib: true do expect(subject).to include(link('rails/rails', 'https://github.com/rails/rails')) expect(subject).to include(link('rails/responders', 'https://github.com/rails/responders')) end + + it 'links Git repos' do + expect(subject).to include(link('https://gitlab.example.com/gems/sprockets', 'https://gitlab.example.com/gems/sprockets')) + end end end From ee0b2f34184a117a9ea5a4431c1433bb0a1d1f0a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 12 May 2017 11:20:33 -0500 Subject: [PATCH 7/8] Remove unrelated link from spec --- spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb | 7 ------- 1 file changed, 7 deletions(-) diff --git a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb index 024a03a0ef5..2e52097a946 100644 --- a/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb +++ b/spec/lib/gitlab/dependency_linker/gemfile_linker_spec.rb @@ -24,15 +24,8 @@ describe Gitlab::DependencyLinker::GemfileLinker, lib: true do gem "rails", '4.2.6', github: "rails/rails" gem 'rails-deprecated_sanitizer', '~> 1.0.3' - - # Responders respond_to and respond_with gem 'responders', '~> 2.0', :github => 'rails/responders' - - # Specify a sprockets version due to increased performance - # See https://gitlab.com/gitlab-org/gitlab-ce/issues/6069 gem 'sprockets', '~> 3.6.0', git: 'https://gitlab.example.com/gems/sprockets' - - # Default values for AR models gem 'default_value_for', '~> 3.0.0' CONTENT end From b6ad80501577dcb09190d56333c035d59984b2a4 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 12 May 2017 11:20:54 -0500 Subject: [PATCH 8/8] Add changelog --- changelogs/unreleased/dm-dependency-linker-gemfile.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/dm-dependency-linker-gemfile.yml diff --git a/changelogs/unreleased/dm-dependency-linker-gemfile.yml b/changelogs/unreleased/dm-dependency-linker-gemfile.yml new file mode 100644 index 00000000000..2d4167a1be5 --- /dev/null +++ b/changelogs/unreleased/dm-dependency-linker-gemfile.yml @@ -0,0 +1,4 @@ +--- +title: Autolink package names in Gemfile +merge_request: +author: