New upstream version 2.8.0

parent 384703b1
......@@ -4,6 +4,7 @@
.config
.yardoc
Gemfile.lock
gemfiles/*.lock
InstalledFiles
_yardoc
coverage
......
language: ruby
sudo: false
cache: bundler
bundler_args: --path ../../vendor/bundle
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq libicu-dev
addons:
apt:
sources:
- libicu-dev
- kalakris-cmake
packages:
- cmake
script: "bundle exec rake"
script: bundle exec rake
gemfile:
- gemfiles/rails_5.gemfile
- gemfiles/rails_4.gemfile
- gemfiles/rails_3.gemfile
rvm:
- 1.9.2
- 1.9.3
- 2.0.0
- 2.1.1
- ree
- 2.4.0
- 2.3.1
- 2.2.5
- ruby-head
matrix:
fast_finish: true
allow_failures:
- rvm: ree
- rvm: ruby-head
exclude:
- gemfile: gemfiles/rails_4.gemfile
rvm: 2.4.0
- gemfile: gemfiles/rails_3.gemfile
rvm: 2.4.0
appraise 'rails-3' do
gem 'rack', '< 2'
gem 'rails', '3.2.22.2'
end
appraise 'rails-4' do
gem 'rack', '< 2'
gem 'rails', '~> 4.2.6'
end
appraise 'rails-5' do
gem 'rails', '~> 5.0.0'
end
# CHANGELOG
## 2.6.0
* Switch from github-markdown to CommonMark #274
* Fixed a few warnings
## 2.5.0
* Ruby 2.4 support. Backwards compatible, but bumped minor version so projects can choose to lock at older version [#268](https://github.com/jch/html-pipeline/pull/268)
## 2.4.2
* Make EmojiFilter generated img tag HTML attributes configurable [#258](https://github.com/jch/html-pipeline/pull/258)
## 2.4.1
* Regression in EmailReplyPipeline: unfiltered content is being omitted [#253](https://github.com/jch/html-pipeline/pull/253)
## 2.4.0
* Optionally filter email addresses [#247](https://github.com/jch/html-pipeline/pull/247)
## 2.3.0
* Add option to pass in an anchor icon, instead of using octicons [#244](https://github.com/jch/html-pipeline/pull/244)
## 2.2.4
* Use entire namespace so MissingDependencyError constant is resolved [#243](https://github.com/jch/html-pipeline/pull/243)
## 2.2.3
* raise MissingDependencyError instead of aborting on missing dependency [#241](https://github.com/jch/html-pipeline/pull/241)
* Fix typo [#239](https://github.com/jch/html-pipeline/pull/239)
* Test against Ruby 2.3.0 on Travis CI [#238](https://github.com/jch/html-pipeline/pull/238)
* use travis containers [#237](https://github.com/jch/html-pipeline/pull/237)
## 2.2.2
* Fix for calling mention_link_filter with only one argument [#230](https://github.com/jch/html-pipeline/pull/230)
* Add html-pipeline-linkify_github to 3rd Party Extensions in README [#228](https://github.com/jch/html-pipeline/pull/228)
## 2.2.1
* Soften Nokogiri dependency to versions ">= 1.4" [#208](https://github.com/jch/html-pipeline/pull/208)
## 2.2.0
* Only allow cite attribute on blockquote and restrict schemes [#223](https://github.com/jch/html-pipeline/pull/223)
## 2.1.0
* Whitelist schemes for longdesc [#221](https://github.com/jch/html-pipeline/pull/221)
* Extract emoji image tag generation to own method [#195](https://github.com/jch/html-pipeline/pull/195)
* Update README.md [#211](https://github.com/jch/html-pipeline/pull/211)
* Add ImageFilter for image url to img tag conversion [#207](https://github.com/jch/html-pipeline/pull/207)
## 2.0
**New**
* Implement new EmojiFilter context option: ignored_ancestor_tags to accept more ignored tags. [#170](https://github.com/jch/html-pipeline/pull/170) @JuanitoFatas
* Add GitHub flavor Markdown Task List extension [#162](https://github.com/jch/html-pipeline/pull/162) @simeonwillbanks
* @mention allow for custom regex to identify usernames. [#157](https://github.com/jch/html-pipeline/pull/157) @brittballard
* EmojiFilter now requires gemoji ~> 2. [#159](https://github.com/jch/html-pipeline/pull/159) @jch
**Changes**
* Restrict nokogiri to >= 1.4, <= 1.6.5 [#176](https://github.com/jch/html-pipeline/pull/176) @simeonwillbanks
* MentionFilter#link_to_mentioned_user: Replace String introspection with Regexp match [#172](https://github.com/jch/html-pipeline/pull/172) @simeonwillbanks
* Whitelist summary and details element. [#171](https://github.com/jch/html-pipeline/pull/171) @JuanitoFatas
* Support ~login for MentionFilter. [#167](https://github.com/jch/html-pipeline/pull/167) @JuanitoFatas
* Revert "Search for text nodes on DocumentFragments without root tags" [#158](https://github.com/jch/html-pipeline/pull/158) @jch
* Drop support for ruby ree, 1.9.2, 1.9.3 [#156](https://github.com/jch/html-pipeline/pull/156) @jch
* Skip EmojiFilter in `<tt>` tags [#147](https://github.com/jch/html-pipeline/pull/147) @moskvax
* Use Linguist lexers [#153](https://github.com/jch/html-pipeline/pull/153) @pchaigno
* Constrain Active Support >= 2, < 5 [#180](https://github.com/jch/html-pipeline/pull/180) @jch
## 1.11.0
* Search for text nodes on DocumentFragments without root tags #146 Razer6
* Don't filter @mentions in <style> tags #145 jch
* Don't filter @mentions in `<style>` tags #145 jch
* Prefer `http_url` in HttpsFilter. `base_url` still works. #142 bkeepers
* Remove duplicate check in EmojiFilter #141 Razer6
......
source "https://rubygems.org"
source 'https://rubygems.org'
# Specify your gem's dependencies in html-pipeline.gemspec
gemspec
group :development do
gem "bundler"
gem "rake"
gem 'appraisal'
gem 'bundler'
gem 'rake'
end
group :test do
gem "minitest", "~> 5.3"
gem "rinku", "~> 1.7", :require => false
gem "gemoji", "~> 1.0", :require => false
gem "RedCloth", "~> 4.2.9", :require => false
gem "github-markdown", "~> 0.5", :require => false
gem "email_reply_parser", "~> 0.5", :require => false
gem 'commonmarker', '~> 0.16', require: false
gem 'email_reply_parser', '~> 0.5', require: false
gem 'gemoji', '~> 2.0', require: false
gem 'minitest'
gem 'RedCloth', '~> 4.2.9', require: false
gem 'rinku', '~> 1.7', require: false
gem 'sanitize', '~> 2.0', require: false
if RUBY_VERSION < "2.1.0"
gem "escape_utils", "~> 0.3", :require => false
gem "github-linguist", "~> 2.6.2", :require => false
else
gem "escape_utils", "~> 1.0", :require => false
gem "github-linguist", "~> 2.10", :require => false
end
if RUBY_VERSION < "1.9.2"
gem "sanitize", ">= 2", "< 2.0.4", :require => false
gem "nokogiri", ">= 1.4", "< 1.6"
else
gem "sanitize", "~> 2.0", :require => false
end
if RUBY_VERSION < "1.9.3"
gem "activesupport", ">= 2", "< 4"
end
gem 'escape_utils', '~> 1.0', require: false
gem 'rouge', '~> 3.1', require: false
end
# HTML::Pipeline [![Build Status](https://secure.travis-ci.org/jch/html-pipeline.png)](http://travis-ci.org/jch/html-pipeline)
# HTML::Pipeline [![Build Status](https://travis-ci.org/jch/html-pipeline.svg?branch=master)](https://travis-ci.org/jch/html-pipeline)
GitHub HTML processing filters and utilities. This module includes a small
framework for defining DOM based content filters and applying them to user
......@@ -78,13 +78,11 @@ Prints:
```html
<p>This is <em>great</em>:</p>
<div class="highlight">
<pre><span class="n">some_code</span><span class="p">(</span><span class="ss">:first</span><span class="p">)</span>
</pre>
</div>
<pre><code>some_code(:first)
</code></pre>
```
To generate CSS for HTML formatted code, use the [pygments.rb](https://github.com/tmm1/pygments.rb#usage) `#css` method. `pygments.rb` is a dependency of the `SyntaxHighlightFilter`.
To generate CSS for HTML formatted code, use the [Rouge CSS Theme](https://github.com/jneen/rouge#css-theme-options) `#css` method. `rouge` is a dependency of the `SyntaxHighlightFilter`.
Some filters take an optional **context** and/or **result** hash. These are
used to pass around arguments and metadata between filters in a pipeline. For
......@@ -141,6 +139,7 @@ NonGFMMarkdownPipeline = Pipeline.new(MarkdownPipeline.filters,
# Pipelines aren't limited to the web. You can use them for email
# processing also.
HtmlEmailPipeline = Pipeline.new [
PlainTextInputFilter,
ImageMaxWidthFilter
], {}
......@@ -164,7 +163,7 @@ EmojiPipeline = Pipeline.new [
* `MarkdownFilter` - convert markdown to html
* `PlainTextInputFilter` - html escape text and wrap the result in a div
* `SanitizationFilter` - whitelist sanitize user markup
* `SyntaxHighlightFilter` - [code syntax highlighter](#syntax-highlighting)
* `SyntaxHighlightFilter` - code syntax highlighter
* `TextileFilter` - convert textile to html
* `TableOfContentsFilter` - anchor headings with name attributes and generate Table of Contents html unordered list linking headings
......@@ -172,21 +171,21 @@ EmojiPipeline = Pipeline.new [
Filter gem dependencies are not bundled; you must bundle the filter's gem
dependencies. The below list details filters with dependencies. For example,
`SyntaxHighlightFilter` uses [github-linguist](https://github.com/github/linguist)
`SyntaxHighlightFilter` uses [rouge](https://github.com/jneen/rouge)
to detect and highlight languages. For example, to use the `SyntaxHighlightFilter`,
add the following to your Gemfile:
```ruby
gem 'github-linguist'
gem 'rouge'
```
* `AutolinkFilter` - `rinku`
* `EmailReplyFilter` - `escape_utils`, `email_reply_parser`
* `EmojiFilter` - `gemoji`
* `MarkdownFilter` - `github-markdown`
* `MarkdownFilter` - `commonmarker`
* `PlainTextInputFilter` - `escape_utils`
* `SanitizationFilter` - `sanitize`
* `SyntaxHighlightFilter` - `github-linguist`
* `SyntaxHighlightFilter` - `rouge`
* `TextileFilter` - `RedCloth`
_Note:_ See [Gemfile](/Gemfile) `:test` block for version requirements.
......@@ -233,13 +232,33 @@ If you have an idea for a filter, propose it as
whether the filter is a common enough use case to belong in this gem, or should be
built as an external gem.
* [html-pipeline-asciidoc_filter](https://github.com/asciidoctor/html-pipeline-asciidoc_filter) - asciidoc support
Here are some extensions people have built:
* [html-pipeline-asciidoc_filter](https://github.com/asciidoctor/html-pipeline-asciidoc_filter)
* [jekyll-html-pipeline](https://github.com/gjtorikian/jekyll-html-pipeline)
* [nanoc-html-pipeline](https://github.com/burnto/nanoc-html-pipeline)
* [html-pipeline-bitly](https://github.com/dewski/html-pipeline-bitly)
* [html-pipeline-cite](https://github.com/lifted-studios/html-pipeline-cite)
* [tilt-html-pipeline](https://github.com/bradgessler/tilt-html-pipeline)
* [html-pipeline-wiki-link'](https://github.com/lifted-studios/html-pipeline-wiki-link) - WikiMedia-style wiki links
* [task_list](https://github.com/github/task_list) - GitHub flavor Markdown Task List
* [html-pipeline-nico_link](https://github.com/rutan/html-pipeline-nico_link) - An HTML::Pipeline filter for [niconico](http://www.nicovideo.jp) description links
* [html-pipeline-gitlab](https://gitlab.com/gitlab-org/html-pipeline-gitlab) - This gem implements various filters for html-pipeline used by GitLab
* [html-pipeline-youtube](https://github.com/st0012/html-pipeline-youtube) - An HTML::Pipeline filter for YouTube links
* [html-pipeline-flickr](https://github.com/st0012/html-pipeline-flickr) - An HTML::Pipeline filter for Flickr links
* [html-pipeline-vimeo](https://github.com/dlackty/html-pipeline-vimeo) - An HTML::Pipeline filter for Vimeo links
* [html-pipeline-hashtag](https://github.com/mr-dxdy/html-pipeline-hashtag) - An HTML::Pipeline filter for hashtags
* [html-pipeline-linkify_github](https://github.com/jollygoodcode/html-pipeline-linkify_github) - An HTML::Pipeline filter to autolink GitHub urls
* [html-pipeline-redcarpet_filter](https://github.com/bmikol/html-pipeline-redcarpet_filter) - Render Markdown source text into Markdown HTML using Redcarpet
* [html-pipeline-typogruby_filter](https://github.com/bmikol/html-pipeline-typogruby_filter) - Add Typogruby text filters to your HTML::Pipeline
* [korgi](https://github.com/jodeci/korgi) - HTML::Pipeline filters for links to Rails resources
## Instrumenting
Filters and Pipelines can be set up to be instrumented when called. The pipeline
must be setup with an [ActiveSupport::Notifications]
(http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html)
must be setup with an
[ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html)
compatible service object and a name. New pipeline objects will default to the
`HTML::Pipeline.default_instrumentation_service` object.
......@@ -285,6 +304,36 @@ service.subscribe "call_pipeline.html_pipeline" do |event, start, ending, transa
end
```
## FAQ
### 1. Why doesn't my pipeline work when there's no root element in the document?
To make a pipeline work on a plain text document, put the `PlainTextInputFilter`
at the beginning of your pipeline. This will wrap the content in a `div` so the
filters have a root element to work with. If you're passing in an HTML fragment,
but it doesn't have a root element, you can wrap the content in a `div`
yourself. For example:
```ruby
EmojiPipeline = Pipeline.new [
PlainTextInputFilter, # <- Wraps input in a div and escapes html tags
EmojiFilter
], context
plain_text = "Gutentag! :wave:"
EmojiPipeline.call(plain_text)
html_fragment = "This is outside of an html element, but <strong>this isn't. :+1:</strong>"
EmojiPipeline.call("<div>#{html_fragment}</div>") # <- Wrap your own html fragments to avoid escaping
```
### 2. How do I customize a whitelist for `SanitizationFilter`s?
`SanitizationFilter::WHITELIST` is the default whitelist used if no `:whitelist`
argument is given in the context. The default is a good starting template for
you to add additional elements. You can either modify the constant's value, or
re-define your own constant and pass that in via the context.
## Contributing
Please review the [Contributing Guide](https://github.com/jch/html-pipeline/blob/master/CONTRIBUTING.md).
......@@ -307,6 +356,9 @@ Project is a member of the [OSS Manifesto](http://ossmanifesto.org/).
This section is for gem maintainers to cut a new version of the gem.
* update lib/html/pipeline/version.rb to next version number X.X.X following [semver](http://semver.org).
* update CHANGELOG.md. Get latest changes with `git log --oneline vLAST_RELEASE..HEAD | grep Merge`
* create a new branch named `release-x.y.z` where `x.y.z` follows [semver](http://semver.org)
* update lib/html/pipeline/version.rb to next version number X.X.X
* update CHANGELOG.md. Prepare a draft with `script/changelog`
* push branch and create a new pull request
* after tests are green, merge to master
* on the master branch, run `script/release`
#!/usr/bin/env rake
require "bundler/gem_tasks"
require 'rubygems'
require 'bundler/setup'
require 'bundler/gem_tasks'
require 'rake/testtask'
Rake::TestTask.new do |t|
t.libs << "test"
t.libs << 'test'
t.test_files = FileList['test/**/*_test.rb']
t.verbose = true
t.warning = false
end
task :default => :test
\ No newline at end of file
task default: :test
......@@ -4,23 +4,23 @@ require 'html/pipeline'
require 'optparse'
# Accept "help", too
ARGV.map!{|a| a == "help" ? "--help" : a }
.map! { |a| a == 'help' ? '--help' : a }
OptionParser.new do |opts|
onParser.new do |opts|
opts.banner = <<-HELP.gsub(/^ /, '')
Usage: html-pipeline [-h] [-f]
html-pipeline [FILTER [FILTER [...]]] < file.md
cat file.md | html-pipeline [FILTER [FILTER [...]]]
cat file.md | html-pipeline [FILTER [FILTER [...]]]
HELP
opts.separator "Options:"
opts.separator 'Options:'
opts.on("-f", "--filters", "List the available filters") do
filters = HTML::Pipeline.constants.grep(/\w+Filter$/).
map{|f| f.to_s.gsub(/Filter$/,'') }
opts.on('-f', '--filters', 'List the available filters') do
filters = HTML::Pipeline.constants.grep(/\w+Filter$/)
.map { |f| f.to_s.gsub(/Filter$/, '') }
# Text filter doesn't work, no call method
filters -= ["Text"]
filters -= ['Text']
abort <<-HELP.gsub(/^ /, '')
Available filters:
......@@ -38,12 +38,12 @@ if ARGV.empty?
HTML::Pipeline::ImageMaxWidthFilter,
HTML::Pipeline::EmojiFilter,
HTML::Pipeline::AutolinkFilter,
HTML::Pipeline::TableOfContentsFilter,
HTML::Pipeline::TableOfContentsFilter
]
# Add syntax highlighting if linguist is present
# Add syntax highlighting if rouge is present
begin
require 'linguist'
require 'rouge'
filters << HTML::Pipeline::SyntaxHighlightFilter
rescue LoadError
end
......@@ -52,7 +52,7 @@ else
def filter_named(name)
case name
when "Text"
when 'Text'
raise NameError # Text filter doesn't work, no call method
end
......@@ -70,9 +70,9 @@ else
end
context = {
:asset_root => "/assets",
:base_url => "/",
:gfm => true
asset_root: '/assets',
base_url: '/',
gfm: true
}
puts HTML::Pipeline.new(filters, context).call(ARGF.read)[:output]
# -*- encoding: utf-8 -*-
require File.expand_path("../lib/html/pipeline/version", __FILE__)
require File.expand_path('../lib/html/pipeline/version', __FILE__)
Gem::Specification.new do |gem|
gem.name = "html-pipeline"
gem.name = 'html-pipeline'
gem.version = HTML::Pipeline::VERSION
gem.license = "MIT"
gem.authors = ["Ryan Tomayko", "Jerry Cheung"]
gem.email = ["ryan@github.com", "jerry@github.com"]
gem.description = %q{GitHub HTML processing filters and utilities}
gem.summary = %q{Helpers for processing content through a chain of filters}
gem.homepage = "https://github.com/jch/html-pipeline"
gem.license = 'MIT'
gem.authors = ['Ryan Tomayko', 'Jerry Cheung']
gem.email = ['ryan@github.com', 'jerry@github.com']
gem.description = 'GitHub HTML processing filters and utilities'
gem.summary = 'Helpers for processing content through a chain of filters'
gem.homepage = 'https://github.com/jch/html-pipeline'
gem.files = `git ls-files`.split $/
gem.test_files = gem.files.grep(%r{^test})
gem.require_paths = ["lib"]
gem.files = `git ls-files -z`.split("\x0").reject { |f| f =~ %r{^(test|gemfiles|script)/} }
gem.require_paths = ['lib']
gem.add_dependency "nokogiri", "~> 1.4"
gem.add_dependency "activesupport", ">= 2"
gem.add_dependency 'activesupport', '>= 2'
gem.add_dependency 'nokogiri', '>= 1.4'
gem.post_install_message = <<msg
-------------------------------------------------
......
require "nokogiri"
require "active_support/xml_mini/nokogiri" # convert Documents to hashes
require 'nokogiri'
require 'active_support/xml_mini/nokogiri' # convert Documents to hashes
module HTML
# GitHub HTML processing filters and utilities. This module includes a small
......@@ -32,6 +32,7 @@ module HTML
autoload :EmailReplyFilter, 'html/pipeline/email_reply_filter'
autoload :EmojiFilter, 'html/pipeline/emoji_filter'
autoload :HttpsFilter, 'html/pipeline/https_filter'
autoload :ImageFilter, 'html/pipeline/image_filter'
autoload :ImageMaxWidthFilter, 'html/pipeline/image_max_width_filter'
autoload :MarkdownFilter, 'html/pipeline/markdown_filter'
autoload :MentionFilter, 'html/pipeline/@mention_filter'
......@@ -42,6 +43,14 @@ module HTML
autoload :TableOfContentsFilter, 'html/pipeline/toc_filter'
autoload :TextFilter, 'html/pipeline/text_filter'
class MissingDependencyError < RuntimeError; end
def self.require_dependency(name, requirer)
require name
rescue LoadError => e
raise MissingDependencyError,
"Missing dependency '#{name}' for #{requirer}. See README.md for details.\n#{e.class.name}: #{e}"
end
# Our DOM implementation.
DocumentFragment = Nokogiri::HTML::DocumentFragment
......@@ -66,7 +75,8 @@ module HTML
# Public: String name for this Pipeline. Defaults to Class name.
attr_writer :instrumentation_name
def instrumentation_name
@instrumentation_name || self.class.name
return @instrumentation_name if defined?(@instrumentation_name)
@instrumentation_name = self.class.name
end
class << self
......@@ -75,7 +85,7 @@ module HTML
end
def initialize(filters, default_context = {}, result_class = nil)
raise ArgumentError, "default_context cannot be nil" if default_context.nil?
raise ArgumentError, 'default_context cannot be nil' if default_context.nil?
@filters = filters.flatten.freeze
@default_context = default_context.freeze
@result_class = result_class || Hash
......@@ -98,9 +108,9 @@ module HTML
context = @default_context.merge(context)
context = context.freeze
result ||= @result_class.new
payload = default_payload :filters => @filters.map(&:name),
:context => context, :result => result
instrument "call_pipeline.html_pipeline", payload do
payload = default_payload filters: @filters.map(&:name),
context: context, result: result
instrument 'call_pipeline.html_pipeline', payload do
result[:output] =
@filters.inject(html) do |doc, filter|
perform_filter(filter, doc, context, result)
......@@ -115,9 +125,9 @@ module HTML
#
# Returns the result of the filter.
def perform_filter(filter, doc, context, result)
payload = default_payload :filter => filter.name,
:context => context, :result => result
instrument "call_filter.html_pipeline", payload do
payload = default_payload filter: filter.name,
context: context, result: result
instrument 'call_filter.html_pipeline', payload do
filter.call(doc, context, result)
end
end
......@@ -168,13 +178,13 @@ module HTML
#
# Returns a Hash.
def default_payload(payload = {})
{:pipeline => instrumentation_name}.merge(payload)
{ pipeline: instrumentation_name }.merge(payload)
end
end
end
# XXX nokogiri monkey patches for 1.8
if not ''.respond_to?(:force_encoding)
unless ''.respond_to?(:force_encoding)
class Nokogiri::XML::Node
# Work around an issue with utf-8 encoded data being erroneously converted to
# ... some other shit when replacing text nodes. See 'utf-8 output 2' in
......@@ -186,8 +196,8 @@ if not ''.respond_to?(:force_encoding)
replace_without_encoding_fix(replacement)
end
alias_method :replace_without_encoding_fix, :replace
alias_method :replace, :replace_with_encoding_fix
alias replace_without_encoding_fix replace
alias replace replace_with_encoding_fix
def swap(replacement)
replace(replacement)
......
......@@ -11,6 +11,8 @@ module HTML
# mention.
# :info_url - Used to link to "more info" when someone mentions @mention
# or @mentioned.
# :username_pattern - Used to provide a custom regular expression to
# identify usernames
#
class MentionFilter < Filter
# Public: Find user @mentions in text. See
......@@ -27,46 +29,52 @@ module HTML
# the original text.
#
# Returns a String replaced with the return of the block.
def self.mentioned_logins_in(text)
text.gsub MentionPattern do |match|
login = $1
def self.mentioned_logins_in(text, username_pattern = UsernamePattern)
text.gsub MentionPatterns[username_pattern] do |match|
login = Regexp.last_match(1)
yield match, login, MentionLogins.include?(login.downcase)
end
end
# Pattern used to extract @mentions from text.
MentionPattern = /
(?:^|\W) # beginning of string or non-word char
@((?>[a-z0-9][a-z0-9-]*)) # @username
(?!\/) # without a trailing slash
(?=
\.+[ \t\W]| # dots followed by space or non-word character
\.+$| # dots at end of line
[^0-9a-zA-Z_.]| # non-word character except dot
$ # end of line
)
/ix
# Hash that contains all of the mention patterns used by the pipeline
MentionPatterns = Hash.new do |hash, key|
hash[key] = /
(?:^|\W) # beginning of string or non-word char
@((?>#{key})) # @username
(?!\/) # without a trailing slash
(?=
\.+[ \t\W]| # dots followed by space or non-word character
\.+$| # dots at end of line
[^0-9a-zA-Z_.]| # non-word character except dot
$ # end of line
)
/ix
end
# Default pattern used to extract usernames from text. The value can be
# overriden by providing the username_pattern variable in the context.
UsernamePattern = /[a-z0-9][a-z0-9-]*/
# List of username logins that, when mentioned, link to the blog post
# about @mentions instead of triggering a real mention.
MentionLogins = %w(
MentionLogins = %w[
mention
mentions
mentioned
mentioning
)
].freeze
# Don't look for mentions in text nodes that are children of these elements
IGNORE_PARENTS = %w(pre code a style).to_set
IGNORE_PARENTS = %w(pre code a style script).to_set
def call
result[:mentioned_usernames] ||= []
search_text_nodes(doc).each do |node|
doc.search('.//text()').each do |node|
content = node.to_html
next if !content.include?('@')
next unless content.include?('@')
next if has_ancestor?(node, IGNORE_PARENTS)
html = mention_link_filter(content, base_url, info_url)
html = mention_link_filter(content, base_url, info_url, username_pattern)
next if html == content
node.replace(html)
end
......@@ -79,6 +87,10 @@ module HTML
context[:info_url] || nil
end
def username_pattern
context[:username_pattern] || UsernamePattern
end
# Replace user @mentions in text with links to the mentioned user's
# profile page.
#
......@@ -86,11 +98,13 @@ module HTML
# base_url - The base URL used to construct user profile URLs.
# info_url - The "more info" URL used to link to more info on @mentions.
# If nil we don't link @mention or @mentioned.
# username_pattern - Regular expression used to identify usernames in
# text
#
# Returns a string with @mentions replaced with links. All links have a
# 'user-mention' class name attached for styling.
def mention_link_filter(text, base_url='/', info_url=nil)
self.class.mentioned_logins_in(text) do |match, login, is_mentioned|
def mention_link_filter(text, _base_url = '/', info_url = nil, username_pattern = UsernamePattern)
self.class.mentioned_logins_in(text, username_pattern) do |match, login, is_mentioned|
link =
if is_mentioned
link_to_mention_info(login, info_url)
......@@ -102,19 +116,22 @@ module HTML
end
end
def link_to_mention_info(text, info_url=nil)
def link_to_mention_info(text, info_url = nil)
return "@#{text}" if info_url.nil?
"<a href='#{info_url}' class='user-mention'>" +
"@#{text}" +
"</a>"
"<a href='#{info_url}' class='user-mention'>" \
"@#{text}" \
'</a>'
end
def link_to_mentioned_user(login)
result[:mentioned_usernames] |= [login]
url = File.join(base_url, login)
"<a href='#{url}' class='user-mention'>" +
"@#{login}" +
"</a>"
url = base_url.dup
url << '/' unless url =~ /[\/~]\z/
"<a href='#{url << login}' class='user-mention'>" \
"@#{login}" \
'</a>'
end
end
end
......
......@@ -2,7 +2,6 @@ require 'uri'
module HTML
class Pipeline
class AbsoluteSourceFilter < Filter
# HTML Filter for replacing relative and root relative image URLs with
# fully qualified URLs
......@@ -18,31 +17,29 @@ module HTML
# This filter does not write additional information to the context.