html-pipeline 1.11.0 → 2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +5 -6
- data/CHANGELOG.md +22 -1
- data/Gemfile +2 -8
- data/README.md +52 -8
- data/html-pipeline.gemspec +2 -2
- data/lib/html/pipeline/@mention_filter.rb +37 -20
- data/lib/html/pipeline/emoji_filter.rb +21 -18
- data/lib/html/pipeline/filter.rb +0 -7
- data/lib/html/pipeline/sanitization_filter.rb +3 -3
- data/lib/html/pipeline/syntax_highlight_filter.rb +5 -1
- data/lib/html/pipeline/version.rb +1 -1
- data/script/changelog +30 -0
- data/test/html/pipeline/emoji_filter_test.rb +41 -12
- data/test/html/pipeline/mention_filter_test.rb +47 -2
- data/test/html/pipeline/sanitization_filter_test.rb +24 -0
- metadata +23 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 81ea1429ab17d2b994718b0332f25cca4587258e
|
4
|
+
data.tar.gz: 1e88eb99b9689beaa37be9f07dd337b587e7f6de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 53dcb279bc816247b352642d0037dfdf7200622f8ec81e0f4e7e2f16f93dfb1131a159274cac6cd37822b6b9486f4160d20e78ec439a4a3a4731d10da23b60a7
|
7
|
+
data.tar.gz: 435e84847668fac1e7f8ea78e6dac82b7e646885523440ce57dc19e711e69875a1b2b0eca5aade300dbbefe0a5c064f4af66a7dbaa5af1d48a0ad1f9ba5b1d7d
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,9 +1,30 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 2.0
|
4
|
+
|
5
|
+
**New**
|
6
|
+
|
7
|
+
* Implement new EmojiFilter context option: ignored_ancestor_tags to accept more ignored tags. [#170](https://github.com/jch/html-pipeline/pull/170) @JuanitoFatas
|
8
|
+
* Add GitHub flavor Markdown Task List extension [#162](https://github.com/jch/html-pipeline/pull/162) @simeonwillbanks
|
9
|
+
* @mention allow for custom regex to identify usernames. [#157](https://github.com/jch/html-pipeline/pull/157) @brittballard
|
10
|
+
* EmojiFilter now requires gemoji ~> 2. [#159](https://github.com/jch/html-pipeline/pull/159) @jch
|
11
|
+
|
12
|
+
**Changes**
|
13
|
+
|
14
|
+
* Restrict nokogiri to >= 1.4, <= 1.6.5 [#176](https://github.com/jch/html-pipeline/pull/176) @simeonwillbanks
|
15
|
+
* MentionFilter#link_to_mentioned_user: Replace String introspection with Regexp match [#172](https://github.com/jch/html-pipeline/pull/172) @simeonwillbanks
|
16
|
+
* Whitelist summary and details element. [#171](https://github.com/jch/html-pipeline/pull/171) @JuanitoFatas
|
17
|
+
* Support ~login for MentionFilter. [#167](https://github.com/jch/html-pipeline/pull/167) @JuanitoFatas
|
18
|
+
* Revert "Search for text nodes on DocumentFragments without root tags" [#158](https://github.com/jch/html-pipeline/pull/158) @jch
|
19
|
+
* Drop support for ruby ree, 1.9.2, 1.9.3 [#156](https://github.com/jch/html-pipeline/pull/156) @jch
|
20
|
+
* Skip EmojiFilter in `<tt>` tags [#147](https://github.com/jch/html-pipeline/pull/147) @moskvax
|
21
|
+
* Use Linguist lexers [#153](https://github.com/jch/html-pipeline/pull/153) @pchaigno
|
22
|
+
* Constrain Active Support >= 2, < 5 [#180](https://github.com/jch/html-pipeline/pull/180) @jch
|
23
|
+
|
3
24
|
## 1.11.0
|
4
25
|
|
5
26
|
* Search for text nodes on DocumentFragments without root tags #146 Razer6
|
6
|
-
* Don't filter @mentions in
|
27
|
+
* Don't filter @mentions in `<style>` tags #145 jch
|
7
28
|
* Prefer `http_url` in HttpsFilter. `base_url` still works. #142 bkeepers
|
8
29
|
* Remove duplicate check in EmojiFilter #141 Razer6
|
9
30
|
|
data/Gemfile
CHANGED
@@ -11,10 +11,11 @@ end
|
|
11
11
|
group :test do
|
12
12
|
gem "minitest", "~> 5.3"
|
13
13
|
gem "rinku", "~> 1.7", :require => false
|
14
|
-
gem "gemoji", "~>
|
14
|
+
gem "gemoji", "~> 2.0", :require => false
|
15
15
|
gem "RedCloth", "~> 4.2.9", :require => false
|
16
16
|
gem "github-markdown", "~> 0.5", :require => false
|
17
17
|
gem "email_reply_parser", "~> 0.5", :require => false
|
18
|
+
gem "sanitize", "~> 2.0", :require => false
|
18
19
|
|
19
20
|
if RUBY_VERSION < "2.1.0"
|
20
21
|
gem "escape_utils", "~> 0.3", :require => false
|
@@ -24,13 +25,6 @@ group :test do
|
|
24
25
|
gem "github-linguist", "~> 2.10", :require => false
|
25
26
|
end
|
26
27
|
|
27
|
-
if RUBY_VERSION < "1.9.2"
|
28
|
-
gem "sanitize", ">= 2", "< 2.0.4", :require => false
|
29
|
-
gem "nokogiri", ">= 1.4", "< 1.6"
|
30
|
-
else
|
31
|
-
gem "sanitize", "~> 2.0", :require => false
|
32
|
-
end
|
33
|
-
|
34
28
|
if RUBY_VERSION < "1.9.3"
|
35
29
|
gem "activesupport", ">= 2", "< 4"
|
36
30
|
end
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# HTML::Pipeline [![Build Status](https://
|
1
|
+
# HTML::Pipeline [![Build Status](https://travis-ci.org/jch/html-pipeline.svg?branch=master)](https://travis-ci.org/jch/html-pipeline)
|
2
2
|
|
3
3
|
GitHub HTML processing filters and utilities. This module includes a small
|
4
4
|
framework for defining DOM based content filters and applying them to user
|
@@ -78,10 +78,8 @@ Prints:
|
|
78
78
|
```html
|
79
79
|
<p>This is <em>great</em>:</p>
|
80
80
|
|
81
|
-
<
|
82
|
-
|
83
|
-
</pre>
|
84
|
-
</div>
|
81
|
+
<pre><code>some_code(:first)
|
82
|
+
</code></pre>
|
85
83
|
```
|
86
84
|
|
87
85
|
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`.
|
@@ -141,6 +139,7 @@ NonGFMMarkdownPipeline = Pipeline.new(MarkdownPipeline.filters,
|
|
141
139
|
# Pipelines aren't limited to the web. You can use them for email
|
142
140
|
# processing also.
|
143
141
|
HtmlEmailPipeline = Pipeline.new [
|
142
|
+
PlainTextInputFilter,
|
144
143
|
ImageMaxWidthFilter
|
145
144
|
], {}
|
146
145
|
|
@@ -233,7 +232,19 @@ If you have an idea for a filter, propose it as
|
|
233
232
|
whether the filter is a common enough use case to belong in this gem, or should be
|
234
233
|
built as an external gem.
|
235
234
|
|
236
|
-
|
235
|
+
Here are some extensions people have built:
|
236
|
+
|
237
|
+
* [html-pipeline-asciidoc_filter](https://github.com/asciidoctor/html-pipeline-asciidoc_filter)
|
238
|
+
* [jekyll-html-pipeline](https://github.com/gjtorikian/jekyll-html-pipeline)
|
239
|
+
* [nanoc-html-pipeline](https://github.com/burnto/nanoc-html-pipeline)
|
240
|
+
* [html-pipeline-bity](https://github.com/dewski/html-pipeline-bitly)
|
241
|
+
* [html-pipeline-cite](https://github.com/lifted-studios/html-pipeline-cite)
|
242
|
+
* [tilt-html-pipeline](https://github.com/bradgessler/tilt-html-pipeline)
|
243
|
+
* [html-pipeline-wiki-link'](https://github.com/lifted-studios/html-pipeline-wiki-link) - WikiMedia-style wiki links
|
244
|
+
* [task_list](https://github.com/github/task_list) - GitHub flavor Markdown Task List
|
245
|
+
* [html-pipeline-rouge_filter](https://github.com/JuanitoFatas/html-pipeline-rouge_filter) - Syntax highlight with [Rouge](https://github.com/jneen/rouge/)
|
246
|
+
* [html-pipeline-nico_link](https://github.com/rutan/html-pipeline-nico_link) - An HTML::Pipeline filter for [niconico](http://www.nicovideo.jp) description links
|
247
|
+
* [html-pipeline-gitlab](https://gitlab.com/gitlab-org/html-pipeline-gitlab) - This gem implements various filters for html-pipeline used by GitLab
|
237
248
|
|
238
249
|
## Instrumenting
|
239
250
|
|
@@ -285,6 +296,36 @@ service.subscribe "call_pipeline.html_pipeline" do |event, start, ending, transa
|
|
285
296
|
end
|
286
297
|
```
|
287
298
|
|
299
|
+
## FAQ
|
300
|
+
|
301
|
+
### 1. Why doesn't my pipeline work when there's no root element in the document?
|
302
|
+
|
303
|
+
To make a pipeline work on a plain text document, put the `PlainTextInputFilter`
|
304
|
+
at the beginning of your pipeline. This will wrap the content in a `div` so the
|
305
|
+
filters have a root element to work with. If you're passing in an HTML fragment,
|
306
|
+
but it doesn't have a root element, you can wrap the content in a `div`
|
307
|
+
yourself. For example:
|
308
|
+
|
309
|
+
```ruby
|
310
|
+
EmojiPipeline = Pipeline.new [
|
311
|
+
PlainTextInputFilter, # <- Wraps input in a div and escapes html tags
|
312
|
+
EmojiFilter
|
313
|
+
], context
|
314
|
+
|
315
|
+
plain_text = "Gutentag! :wave:"
|
316
|
+
EmojiPipeline.call(plain_text)
|
317
|
+
|
318
|
+
html_fragment = "This is outside of an html element, but <strong>this isn't. :+1:</strong>"
|
319
|
+
EmojiPipeline.call("<div>#{html_fragment}</div>") # <- Wrap your own html fragments to avoid escaping
|
320
|
+
```
|
321
|
+
|
322
|
+
### 2. How do I customize a whitelist for `SanitizationFilter`s?
|
323
|
+
|
324
|
+
`SanitizationFilter::WHITELIST` is the default whitelist used if no `:whitelist`
|
325
|
+
argument is given in the context. The default is a good starting template for
|
326
|
+
you to add additional elements. You can either modify the constant's value, or
|
327
|
+
re-define your own constant and pass that in via the context.
|
328
|
+
|
288
329
|
## Contributing
|
289
330
|
|
290
331
|
Please review the [Contributing Guide](https://github.com/jch/html-pipeline/blob/master/CONTRIBUTING.md).
|
@@ -307,6 +348,9 @@ Project is a member of the [OSS Manifesto](http://ossmanifesto.org/).
|
|
307
348
|
|
308
349
|
This section is for gem maintainers to cut a new version of the gem.
|
309
350
|
|
310
|
-
*
|
311
|
-
* update
|
351
|
+
* create a new branch named `release-x.y.z` where `x.y.z` follows [semver](http://semver.org)
|
352
|
+
* update lib/html/pipeline/version.rb to next version number X.X.X
|
353
|
+
* update CHANGELOG.md. Prepare a draft with `script/changelog`
|
354
|
+
* push branch and create a new pull request
|
355
|
+
* after tests are green, merge to master
|
312
356
|
* on the master branch, run `script/release`
|
data/html-pipeline.gemspec
CHANGED
@@ -15,8 +15,8 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.test_files = gem.files.grep(%r{^test})
|
16
16
|
gem.require_paths = ["lib"]
|
17
17
|
|
18
|
-
gem.add_dependency "nokogiri", "
|
19
|
-
gem.add_dependency "activesupport", ">= 2"
|
18
|
+
gem.add_dependency "nokogiri", [">= 1.4", "<= 1.6.5"]
|
19
|
+
gem.add_dependency "activesupport", [">= 2", "< 5"]
|
20
20
|
|
21
21
|
gem.post_install_message = <<msg
|
22
22
|
-------------------------------------------------
|
@@ -11,6 +11,8 @@ module HTML
|
|
11
11
|
# mention.
|
12
12
|
# :info_url - Used to link to "more info" when someone mentions @mention
|
13
13
|
# or @mentioned.
|
14
|
+
# :username_pattern - Used to provide a custom regular expression to
|
15
|
+
# identify usernames
|
14
16
|
#
|
15
17
|
class MentionFilter < Filter
|
16
18
|
# Public: Find user @mentions in text. See
|
@@ -27,25 +29,31 @@ module HTML
|
|
27
29
|
# the original text.
|
28
30
|
#
|
29
31
|
# Returns a String replaced with the return of the block.
|
30
|
-
def self.mentioned_logins_in(text)
|
31
|
-
text.gsub
|
32
|
+
def self.mentioned_logins_in(text, username_pattern=UsernamePattern)
|
33
|
+
text.gsub MentionPatterns[username_pattern] do |match|
|
32
34
|
login = $1
|
33
35
|
yield match, login, MentionLogins.include?(login.downcase)
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
37
|
-
#
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
39
|
+
# Hash that contains all of the mention patterns used by the pipeline
|
40
|
+
MentionPatterns = Hash.new do |hash, key|
|
41
|
+
hash[key] = /
|
42
|
+
(?:^|\W) # beginning of string or non-word char
|
43
|
+
@((?>#{key})) # @username
|
44
|
+
(?!\/) # without a trailing slash
|
45
|
+
(?=
|
46
|
+
\.+[ \t\W]| # dots followed by space or non-word character
|
47
|
+
\.+$| # dots at end of line
|
48
|
+
[^0-9a-zA-Z_.]| # non-word character except dot
|
49
|
+
$ # end of line
|
50
|
+
)
|
51
|
+
/ix
|
52
|
+
end
|
53
|
+
|
54
|
+
# Default pattern used to extract usernames from text. The value can be
|
55
|
+
# overriden by providing the username_pattern variable in the context.
|
56
|
+
UsernamePattern = /[a-z0-9][a-z0-9-]*/
|
49
57
|
|
50
58
|
# List of username logins that, when mentioned, link to the blog post
|
51
59
|
# about @mentions instead of triggering a real mention.
|
@@ -62,11 +70,11 @@ module HTML
|
|
62
70
|
def call
|
63
71
|
result[:mentioned_usernames] ||= []
|
64
72
|
|
65
|
-
|
73
|
+
doc.search('text()').each do |node|
|
66
74
|
content = node.to_html
|
67
75
|
next if !content.include?('@')
|
68
76
|
next if has_ancestor?(node, IGNORE_PARENTS)
|
69
|
-
html = mention_link_filter(content, base_url, info_url)
|
77
|
+
html = mention_link_filter(content, base_url, info_url, username_pattern)
|
70
78
|
next if html == content
|
71
79
|
node.replace(html)
|
72
80
|
end
|
@@ -79,6 +87,10 @@ module HTML
|
|
79
87
|
context[:info_url] || nil
|
80
88
|
end
|
81
89
|
|
90
|
+
def username_pattern
|
91
|
+
context[:username_pattern] || UsernamePattern
|
92
|
+
end
|
93
|
+
|
82
94
|
# Replace user @mentions in text with links to the mentioned user's
|
83
95
|
# profile page.
|
84
96
|
#
|
@@ -86,11 +98,13 @@ module HTML
|
|
86
98
|
# base_url - The base URL used to construct user profile URLs.
|
87
99
|
# info_url - The "more info" URL used to link to more info on @mentions.
|
88
100
|
# If nil we don't link @mention or @mentioned.
|
101
|
+
# username_pattern - Regular expression used to identify usernames in
|
102
|
+
# text
|
89
103
|
#
|
90
104
|
# Returns a string with @mentions replaced with links. All links have a
|
91
105
|
# 'user-mention' class name attached for styling.
|
92
|
-
def mention_link_filter(text, base_url='/', info_url=nil)
|
93
|
-
self.class.mentioned_logins_in(text) do |match, login, is_mentioned|
|
106
|
+
def mention_link_filter(text, base_url='/', info_url=nil, username_pattern)
|
107
|
+
self.class.mentioned_logins_in(text, username_pattern) do |match, login, is_mentioned|
|
94
108
|
link =
|
95
109
|
if is_mentioned
|
96
110
|
link_to_mention_info(login, info_url)
|
@@ -111,8 +125,11 @@ module HTML
|
|
111
125
|
|
112
126
|
def link_to_mentioned_user(login)
|
113
127
|
result[:mentioned_usernames] |= [login]
|
114
|
-
|
115
|
-
|
128
|
+
|
129
|
+
url = base_url.dup
|
130
|
+
url << "/" unless url =~ /[\/~]\z/
|
131
|
+
|
132
|
+
"<a href='#{url << login}' class='user-mention'>" +
|
116
133
|
"@#{login}" +
|
117
134
|
"</a>"
|
118
135
|
end
|
@@ -13,19 +13,23 @@ module HTML
|
|
13
13
|
# Context:
|
14
14
|
# :asset_root (required) - base url to link to emoji sprite
|
15
15
|
# :asset_path (optional) - url path to link to emoji sprite. :file_name can be used as a placeholder for the sprite file name. If no asset_path is set "emoji/:file_name" is used.
|
16
|
+
# :ignored_ancestor_tags (optional) - Tags to stop the emojification. Node has matched ancestor HTML tags will not be emojified. Default to pre, code, and tt tags. Extra tags please pass in the form of array, e.g., %w(blockquote summary).
|
16
17
|
class EmojiFilter < Filter
|
18
|
+
|
19
|
+
DEFAULT_IGNORED_ANCESTOR_TAGS = %w(pre code tt).freeze
|
20
|
+
|
17
21
|
def call
|
18
|
-
|
22
|
+
doc.search('text()').each do |node|
|
19
23
|
content = node.to_html
|
20
24
|
next unless content.include?(':')
|
21
|
-
next if has_ancestor?(node,
|
25
|
+
next if has_ancestor?(node, ignored_ancestor_tags)
|
22
26
|
html = emoji_image_filter(content)
|
23
27
|
next if html == content
|
24
28
|
node.replace(html)
|
25
29
|
end
|
26
30
|
doc
|
27
31
|
end
|
28
|
-
|
32
|
+
|
29
33
|
# Implementation of validate hook.
|
30
34
|
# Errors should raise exceptions or use an existing validator.
|
31
35
|
def validate
|
@@ -79,23 +83,22 @@ module HTML
|
|
79
83
|
self.class.emoji_pattern
|
80
84
|
end
|
81
85
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
def self.emoji_names
|
86
|
-
Emoji.all.map(&:aliases).flatten.sort
|
87
|
-
end
|
86
|
+
def self.emoji_names
|
87
|
+
Emoji.all.map(&:aliases).flatten.sort
|
88
|
+
end
|
88
89
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
else
|
93
|
-
def self.emoji_names
|
94
|
-
Emoji.names
|
95
|
-
end
|
90
|
+
def emoji_filename(name)
|
91
|
+
Emoji.find_by_alias(name).image_filename
|
92
|
+
end
|
96
93
|
|
97
|
-
|
98
|
-
|
94
|
+
# Return ancestor tags to stop the emojification.
|
95
|
+
#
|
96
|
+
# @return [Array<String>] Ancestor tags.
|
97
|
+
def ignored_ancestor_tags
|
98
|
+
if context[:ignored_ancestor_tags]
|
99
|
+
DEFAULT_IGNORED_ANCESTOR_TAGS | context[:ignored_ancestor_tags]
|
100
|
+
else
|
101
|
+
DEFAULT_IGNORED_ANCESTOR_TAGS
|
99
102
|
end
|
100
103
|
end
|
101
104
|
end
|
data/lib/html/pipeline/filter.rb
CHANGED
@@ -59,13 +59,6 @@ module HTML
|
|
59
59
|
@doc ||= parse_html(html)
|
60
60
|
end
|
61
61
|
|
62
|
-
# Searches a Nokogiri::HTML::DocumentFragment for text nodes. If no elements
|
63
|
-
# are found, a second search without root tags is invoked.
|
64
|
-
def search_text_nodes(doc)
|
65
|
-
nodes = doc.xpath('.//text()')
|
66
|
-
nodes.empty? ? doc.xpath('text()') : nodes
|
67
|
-
end
|
68
|
-
|
69
62
|
# The String representation of the document. If a DocumentFragment was
|
70
63
|
# provided to the Filter, it is serialized into a String when this method is
|
71
64
|
# called.
|
@@ -45,7 +45,7 @@ module HTML
|
|
45
45
|
:elements => %w(
|
46
46
|
h1 h2 h3 h4 h5 h6 h7 h8 br b i strong em a pre code img tt
|
47
47
|
div ins del sup sub p ol ul table thead tbody tfoot blockquote
|
48
|
-
dl dt dd kbd q samp var hr ruby rt rp li tr td th s strike
|
48
|
+
dl dt dd kbd q samp var hr ruby rt rp li tr td th s strike summary details
|
49
49
|
),
|
50
50
|
:remove_contents => ['script'],
|
51
51
|
:attributes => {
|
@@ -57,13 +57,13 @@ module HTML
|
|
57
57
|
'border', 'cellpadding', 'cellspacing', 'char',
|
58
58
|
'charoff', 'charset', 'checked', 'cite',
|
59
59
|
'clear', 'cols', 'colspan', 'color',
|
60
|
-
'compact', 'coords', 'datetime', '
|
60
|
+
'compact', 'coords', 'datetime', 'dir',
|
61
61
|
'disabled', 'enctype', 'for', 'frame',
|
62
62
|
'headers', 'height', 'hreflang',
|
63
63
|
'hspace', 'ismap', 'label', 'lang',
|
64
64
|
'longdesc', 'maxlength', 'media', 'method',
|
65
65
|
'multiple', 'name', 'nohref', 'noshade',
|
66
|
-
'nowrap', 'prompt', 'readonly', 'rel', 'rev',
|
66
|
+
'nowrap', 'open', 'prompt', 'readonly', 'rel', 'rev',
|
67
67
|
'rows', 'rowspan', 'rules', 'scope',
|
68
68
|
'selected', 'shape', 'size', 'span',
|
69
69
|
'start', 'summary', 'tabindex', 'target',
|
@@ -13,7 +13,7 @@ module HTML
|
|
13
13
|
doc.search('pre').each do |node|
|
14
14
|
default = context[:highlight] && context[:highlight].to_s
|
15
15
|
next unless lang = node['lang'] || default
|
16
|
-
next unless lexer =
|
16
|
+
next unless lexer = lexer_for(lang)
|
17
17
|
text = node.inner_text
|
18
18
|
|
19
19
|
html = highlight_with_timeout_handling(lexer, text)
|
@@ -34,6 +34,10 @@ module HTML
|
|
34
34
|
rescue Timeout::Error => boom
|
35
35
|
nil
|
36
36
|
end
|
37
|
+
|
38
|
+
def lexer_for(lang)
|
39
|
+
(Linguist::Language[lang] && Linguist::Language[lang].lexer) || Pygments::Lexer[lang]
|
40
|
+
end
|
37
41
|
end
|
38
42
|
end
|
39
43
|
end
|
data/script/changelog
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env sh
|
2
|
+
# Usage: script/changelog [-r <repo>] [-b <base>] [-h <head>]
|
3
|
+
#
|
4
|
+
# repo: base string of GitHub repository url. e.g. "user_or_org/repository". Defaults to git remote url.
|
5
|
+
# base: git ref to compare from. e.g. "v1.3.1". Defaults to latest git tag.
|
6
|
+
# head: git ref to compare to. Defaults to "HEAD".
|
7
|
+
#
|
8
|
+
# Generate a changelog preview from pull requests merged between `base` and
|
9
|
+
# `head`.
|
10
|
+
#
|
11
|
+
# https://github.com/jch/release-scripts
|
12
|
+
set -e
|
13
|
+
|
14
|
+
[ $# -eq 0 ] && set -- --help
|
15
|
+
|
16
|
+
# parse args
|
17
|
+
repo='jch/html-pipeline'
|
18
|
+
base=$(git tag -l | sort -t. -k 1,1n -k 2,2n -k 3,3n | tail -n 1)
|
19
|
+
head="HEAD"
|
20
|
+
api_url="https://api.github.com"
|
21
|
+
|
22
|
+
echo "# $repo $base..$head"
|
23
|
+
echo
|
24
|
+
|
25
|
+
# get merged PR's. Better way is to query the API for these, but this is easier
|
26
|
+
for pr in $(git log --oneline $base..$head | grep "Merge pull request" | awk '{gsub("#",""); print $5}')
|
27
|
+
do
|
28
|
+
# frustrated with trying to pull out the right values, fell back to ruby
|
29
|
+
curl -s "$api_url/repos/$repo/pulls/$pr" | ruby -rjson -e 'pr=JSON.parse(STDIN.read); puts "* #{pr[%q(title)]} [##{pr[%q(number)]}](#{pr[%q(html_url)]}) @#{pr[%q(user)][%q(login)]}"'
|
30
|
+
done
|
@@ -2,28 +2,22 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class HTML::Pipeline::EmojiFilterTest < Minitest::Test
|
4
4
|
EmojiFilter = HTML::Pipeline::EmojiFilter
|
5
|
-
|
5
|
+
|
6
6
|
def test_emojify
|
7
7
|
filter = EmojiFilter.new("<p>:shipit:</p>", {:asset_root => 'https://foo.com'})
|
8
8
|
doc = filter.call
|
9
9
|
assert_match "https://foo.com/emoji/shipit.png", doc.search('img').attr('src').value
|
10
10
|
end
|
11
11
|
|
12
|
-
def test_emojify_on_string
|
13
|
-
filter = EmojiFilter.new(":shipit:", {:asset_root => 'https://foo.com'})
|
14
|
-
doc = filter.call
|
15
|
-
assert_match "https://foo.com/emoji/shipit.png", doc.search('img').attr('src').value
|
16
|
-
end
|
17
|
-
|
18
12
|
def test_uri_encoding
|
19
13
|
filter = EmojiFilter.new("<p>:+1:</p>", {:asset_root => 'https://foo.com'})
|
20
14
|
doc = filter.call
|
21
|
-
assert_match "https://foo.com/emoji
|
15
|
+
assert_match "https://foo.com/emoji/unicode/1f44d.png", doc.search('img').attr('src').value
|
22
16
|
end
|
23
|
-
|
17
|
+
|
24
18
|
def test_required_context_validation
|
25
|
-
exception = assert_raises(ArgumentError) {
|
26
|
-
EmojiFilter.call("", {})
|
19
|
+
exception = assert_raises(ArgumentError) {
|
20
|
+
EmojiFilter.call("", {})
|
27
21
|
}
|
28
22
|
assert_match /:asset_root/, exception.message
|
29
23
|
end
|
@@ -31,6 +25,41 @@ class HTML::Pipeline::EmojiFilterTest < Minitest::Test
|
|
31
25
|
def test_custom_asset_path
|
32
26
|
filter = EmojiFilter.new("<p>:+1:</p>", {:asset_path => ':file_name', :asset_root => 'https://foo.com'})
|
33
27
|
doc = filter.call
|
34
|
-
assert_match "https://foo.com
|
28
|
+
assert_match "https://foo.com/unicode/1f44d.png", doc.search('img').attr('src').value
|
29
|
+
end
|
30
|
+
|
31
|
+
def test_not_emojify_in_code_tags
|
32
|
+
body = "<code>:shipit:</code>"
|
33
|
+
filter = EmojiFilter.new(body, {:asset_root => 'https://foo.com'})
|
34
|
+
doc = filter.call
|
35
|
+
assert_equal body, doc.to_html
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_not_emojify_in_tt_tags
|
39
|
+
body = "<tt>:shipit:</tt>"
|
40
|
+
filter = EmojiFilter.new(body, {:asset_root => 'https://foo.com'})
|
41
|
+
doc = filter.call
|
42
|
+
assert_equal body, doc.to_html
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_not_emojify_in_pre_tags
|
46
|
+
body = "<pre>:shipit:</pre>"
|
47
|
+
filter = EmojiFilter.new(body, {:asset_root => 'https://foo.com'})
|
48
|
+
doc = filter.call
|
49
|
+
assert_equal body, doc.to_html
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_not_emojify_in_custom_single_tag_foo
|
53
|
+
body = "<foo>:shipit:</foo>"
|
54
|
+
filter = EmojiFilter.new(body, {:asset_root => 'https://foo.com', ignored_ancestor_tags: %w(foo)})
|
55
|
+
doc = filter.call
|
56
|
+
assert_equal body, doc.to_html
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_not_emojify_in_custom_multiple_tags_foo_and_bar
|
60
|
+
body = "<bar>:shipit:</bar>"
|
61
|
+
filter = EmojiFilter.new(body, {:asset_root => 'https://foo.com', ignored_ancestor_tags: %w(foo bar)})
|
62
|
+
doc = filter.call
|
63
|
+
assert_equal body, doc.to_html
|
35
64
|
end
|
36
65
|
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require "test_helper"
|
2
2
|
|
3
3
|
class HTML::Pipeline::MentionFilterTest < Minitest::Test
|
4
|
-
def filter(html, base_url='/', info_url=nil)
|
5
|
-
HTML::Pipeline::MentionFilter.call(html, :base_url => base_url, :info_url => info_url)
|
4
|
+
def filter(html, base_url='/', info_url=nil, username_pattern=nil)
|
5
|
+
HTML::Pipeline::MentionFilter.call(html, :base_url => base_url, :info_url => info_url, :username_pattern => username_pattern)
|
6
6
|
end
|
7
7
|
|
8
8
|
def test_filtering_a_documentfragment
|
@@ -72,6 +72,27 @@ class HTML::Pipeline::MentionFilterTest < Minitest::Test
|
|
72
72
|
filter(body, '/', 'https://github.com/blog/821').to_html
|
73
73
|
end
|
74
74
|
|
75
|
+
def test_base_url_slash
|
76
|
+
body = "<p>Hi, @jch!</p>"
|
77
|
+
link = "<a href=\"/jch\" class=\"user-mention\">@jch</a>"
|
78
|
+
assert_equal "<p>Hi, #{link}!</p>",
|
79
|
+
filter(body, '/').to_html
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_base_url_under_custom_route
|
83
|
+
body = "<p>Hi, @jch!</p>"
|
84
|
+
link = "<a href=\"/userprofile/jch\" class=\"user-mention\">@jch</a>"
|
85
|
+
assert_equal "<p>Hi, #{link}!</p>",
|
86
|
+
filter(body, '/userprofile').to_html
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_base_url_slash_with_tilde
|
90
|
+
body = "<p>Hi, @jch!</p>"
|
91
|
+
link = "<a href=\"/~jch\" class=\"user-mention\">@jch</a>"
|
92
|
+
assert_equal "<p>Hi, #{link}!</p>",
|
93
|
+
filter(body, '/~').to_html
|
94
|
+
end
|
95
|
+
|
75
96
|
MarkdownPipeline =
|
76
97
|
HTML::Pipeline.new [
|
77
98
|
HTML::Pipeline::MarkdownFilter,
|
@@ -158,4 +179,28 @@ class HTML::Pipeline::MentionFilterTest < Minitest::Test
|
|
158
179
|
@body = "(We're talking 'bout @ymendel.)"
|
159
180
|
assert_equal %w[ymendel], mentioned_usernames
|
160
181
|
end
|
182
|
+
|
183
|
+
def test_username_pattern_can_be_customized
|
184
|
+
body = "<p>@_abc: test.</p>"
|
185
|
+
doc = Nokogiri::HTML::DocumentFragment.parse(body)
|
186
|
+
|
187
|
+
res = filter(doc, '/', nil, /(_[a-z]{3})/)
|
188
|
+
|
189
|
+
link = "<a href=\"/_abc\" class=\"user-mention\">@_abc</a>"
|
190
|
+
assert_equal "<p>#{link}: test.</p>",
|
191
|
+
res.to_html
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_filter_does_not_create_a_new_object_for_default_username_pattern
|
195
|
+
body = "<div>@test</div>"
|
196
|
+
doc = Nokogiri::HTML::DocumentFragment.parse(body)
|
197
|
+
|
198
|
+
filter(doc.clone, '/', nil)
|
199
|
+
pattern_count = HTML::Pipeline::MentionFilter::MentionPatterns.length
|
200
|
+
filter(doc.clone, '/', nil)
|
201
|
+
|
202
|
+
assert_equal pattern_count, HTML::Pipeline::MentionFilter::MentionPatterns.length
|
203
|
+
filter(doc.clone, '/', nil, /test/)
|
204
|
+
assert_equal pattern_count + 1, HTML::Pipeline::MentionFilter::MentionPatterns.length
|
205
|
+
end
|
161
206
|
end
|
@@ -127,4 +127,28 @@ class HTML::Pipeline::SanitizationFilterTest < Minitest::Test
|
|
127
127
|
</table>)
|
128
128
|
assert_equal orig, SanitizationFilter.call(orig).to_s
|
129
129
|
end
|
130
|
+
|
131
|
+
def test_summary_tag_are_not_removed
|
132
|
+
orig = %(<summary>Foo</summary>)
|
133
|
+
assert_equal orig, SanitizationFilter.call(orig).to_s
|
134
|
+
end
|
135
|
+
|
136
|
+
def test_details_tag_and_open_attribute_are_not_removed
|
137
|
+
orig = %(<details open>Foo</details>)
|
138
|
+
assert_equal orig, SanitizationFilter.call(orig).to_s
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_nested_details_tag_are_not_removed
|
142
|
+
orig = <<-NESTED
|
143
|
+
<details>
|
144
|
+
<summary>Foo</summary>
|
145
|
+
<details>
|
146
|
+
Bar
|
147
|
+
<summary>Baz</summary>
|
148
|
+
</details>
|
149
|
+
Qux
|
150
|
+
</details>
|
151
|
+
NESTED
|
152
|
+
assert_equal orig, SanitizationFilter.call(orig).to_s
|
153
|
+
end
|
130
154
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: html-pipeline
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: '2.0'
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Tomayko
|
@@ -9,36 +9,48 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2015-07-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: nokogiri
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
requirements:
|
18
|
-
- -
|
18
|
+
- - ">="
|
19
19
|
- !ruby/object:Gem::Version
|
20
20
|
version: '1.4'
|
21
|
+
- - "<="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.6.5
|
21
24
|
type: :runtime
|
22
25
|
prerelease: false
|
23
26
|
version_requirements: !ruby/object:Gem::Requirement
|
24
27
|
requirements:
|
25
|
-
- -
|
28
|
+
- - ">="
|
26
29
|
- !ruby/object:Gem::Version
|
27
30
|
version: '1.4'
|
31
|
+
- - "<="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.6.5
|
28
34
|
- !ruby/object:Gem::Dependency
|
29
35
|
name: activesupport
|
30
36
|
requirement: !ruby/object:Gem::Requirement
|
31
37
|
requirements:
|
32
|
-
- -
|
38
|
+
- - ">="
|
33
39
|
- !ruby/object:Gem::Version
|
34
40
|
version: '2'
|
41
|
+
- - "<"
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '5'
|
35
44
|
type: :runtime
|
36
45
|
prerelease: false
|
37
46
|
version_requirements: !ruby/object:Gem::Requirement
|
38
47
|
requirements:
|
39
|
-
- -
|
48
|
+
- - ">="
|
40
49
|
- !ruby/object:Gem::Version
|
41
50
|
version: '2'
|
51
|
+
- - "<"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '5'
|
42
54
|
description: GitHub HTML processing filters and utilities
|
43
55
|
email:
|
44
56
|
- ryan@github.com
|
@@ -47,8 +59,8 @@ executables: []
|
|
47
59
|
extensions: []
|
48
60
|
extra_rdoc_files: []
|
49
61
|
files:
|
50
|
-
- .gitignore
|
51
|
-
- .travis.yml
|
62
|
+
- ".gitignore"
|
63
|
+
- ".travis.yml"
|
52
64
|
- CHANGELOG.md
|
53
65
|
- CONTRIBUTING.md
|
54
66
|
- Gemfile
|
@@ -76,6 +88,7 @@ files:
|
|
76
88
|
- lib/html/pipeline/textile_filter.rb
|
77
89
|
- lib/html/pipeline/toc_filter.rb
|
78
90
|
- lib/html/pipeline/version.rb
|
91
|
+
- script/changelog
|
79
92
|
- script/package
|
80
93
|
- script/release
|
81
94
|
- test/helpers/mocked_instrumentation_service.rb
|
@@ -109,12 +122,12 @@ require_paths:
|
|
109
122
|
- lib
|
110
123
|
required_ruby_version: !ruby/object:Gem::Requirement
|
111
124
|
requirements:
|
112
|
-
- -
|
125
|
+
- - ">="
|
113
126
|
- !ruby/object:Gem::Version
|
114
127
|
version: '0'
|
115
128
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
116
129
|
requirements:
|
117
|
-
- -
|
130
|
+
- - ">="
|
118
131
|
- !ruby/object:Gem::Version
|
119
132
|
version: '0'
|
120
133
|
requirements: []
|