motion-html-pipeline 0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +379 -0
- data/lib/motion-html-pipeline.rb +14 -0
- data/lib/motion-html-pipeline/document_fragment.rb +27 -0
- data/lib/motion-html-pipeline/pipeline.rb +153 -0
- data/lib/motion-html-pipeline/pipeline/absolute_source_filter.rb +45 -0
- data/lib/motion-html-pipeline/pipeline/body_content.rb +42 -0
- data/lib/motion-html-pipeline/pipeline/disabled/@mention_filter.rb +140 -0
- data/lib/motion-html-pipeline/pipeline/disabled/autolink_filter.rb +27 -0
- data/lib/motion-html-pipeline/pipeline/disabled/camo_filter.rb +93 -0
- data/lib/motion-html-pipeline/pipeline/disabled/email_reply_filter.rb +66 -0
- data/lib/motion-html-pipeline/pipeline/disabled/emoji_filter.rb +125 -0
- data/lib/motion-html-pipeline/pipeline/disabled/markdown_filter.rb +37 -0
- data/lib/motion-html-pipeline/pipeline/disabled/plain_text_input_filter.rb +13 -0
- data/lib/motion-html-pipeline/pipeline/disabled/sanitization_filter.rb +137 -0
- data/lib/motion-html-pipeline/pipeline/disabled/syntax_highlight_filter.rb +44 -0
- data/lib/motion-html-pipeline/pipeline/disabled/toc_filter.rb +67 -0
- data/lib/motion-html-pipeline/pipeline/filter.rb +163 -0
- data/lib/motion-html-pipeline/pipeline/https_filter.rb +27 -0
- data/lib/motion-html-pipeline/pipeline/image_filter.rb +17 -0
- data/lib/motion-html-pipeline/pipeline/image_max_width_filter.rb +37 -0
- data/lib/motion-html-pipeline/pipeline/text_filter.rb +14 -0
- data/lib/motion-html-pipeline/pipeline/version.rb +5 -0
- data/spec/motion-html-pipeline/_helpers/mock_instumentation_service.rb +19 -0
- data/spec/motion-html-pipeline/pipeline/absolute_source_filter_spec.rb +47 -0
- data/spec/motion-html-pipeline/pipeline/disabled/auto_link_filter_spec.rb +33 -0
- data/spec/motion-html-pipeline/pipeline/disabled/camo_filter_spec.rb +75 -0
- data/spec/motion-html-pipeline/pipeline/disabled/email_reply_filter_spec.rb +64 -0
- data/spec/motion-html-pipeline/pipeline/disabled/emoji_filter_spec.rb +92 -0
- data/spec/motion-html-pipeline/pipeline/disabled/markdown_filter_spec.rb +112 -0
- data/spec/motion-html-pipeline/pipeline/disabled/plain_text_input_filter_spec.rb +20 -0
- data/spec/motion-html-pipeline/pipeline/disabled/sanitization_filter_spec.rb +164 -0
- data/spec/motion-html-pipeline/pipeline/disabled/syntax_highlighting_filter_spec.rb +59 -0
- data/spec/motion-html-pipeline/pipeline/disabled/toc_filter_spec.rb +137 -0
- data/spec/motion-html-pipeline/pipeline/https_filter_spec.rb +52 -0
- data/spec/motion-html-pipeline/pipeline/image_filter_spec.rb +37 -0
- data/spec/motion-html-pipeline/pipeline/image_max_width_filter_spec.rb +57 -0
- data/spec/motion-html-pipeline/pipeline_spec.rb +80 -0
- data/spec/spec_helper.rb +48 -0
- metadata +147 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 91205fa1d79198a224c28464e12946d1294ac518124dec20b536e86d77d91a49
|
4
|
+
data.tar.gz: 6388828a786942a593664a2759daec71b61582d283a692ffcd0c5d5b1f9f70d3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 12a542cb6b5380268836031a1cfabde939bd32dc3bdb9b71ace8060a97f9c1e170d49ed88c06f79e64e62350eb09b68ec7e10952a21e2547d6c5d8d7db28a10c
|
7
|
+
data.tar.gz: 86091ee0ccca5c6e97ec59a04aca17e23f66a56ab28c5f696d4fd8f6145a021cb946d5e216d321cd28bede723e6df773565387313c4b274fb607504d661eda8b
|
data/README.md
ADDED
@@ -0,0 +1,379 @@
|
|
1
|
+
# motion-html-pipeline
|
2
|
+
|
3
|
+
[](http://badge.fury.io/rb/motion-html-pipeline)
|
4
|
+
[](https://travis-ci.org/digitalmoksha/motion-html-pipeline)
|
5
|
+
|
6
|
+
This gem is a port of the [`html-pipeline` gem](https://github.com/jch/html-pipeline) to RubyMotion, for use on iOS and macOS. Currently synced with `html-pipeline` release [`v.2.11.0`](https://github.com/jch/html-pipeline/releases/tag/v2.11.0)
|
7
|
+
|
8
|
+
Several of the standard filters, such as `AutolinkFilter` and `EmojiFilter`, are initially disabled, as they rely on other Ruby gems that don't have RubyMotion equivalents. Please feel free to submit a pull request that enables any of them.
|
9
|
+
|
10
|
+
We use [HTMLKit](https://github.com/iabudiab/HTMLKit) to take the place of Nokogiri.
|
11
|
+
|
12
|
+
Below is a copy of the original README file, until I'm able to flesh this one out more.
|
13
|
+
|
14
|
+
---
|
15
|
+
# HTML::Pipeline
|
16
|
+
|
17
|
+
GitHub HTML processing filters and utilities. This module includes a small
|
18
|
+
framework for defining DOM based content filters and applying them to user
|
19
|
+
provided content. Read an introduction about this project in
|
20
|
+
[this blog post](https://github.com/blog/1311-html-pipeline-chainable-content-filters).
|
21
|
+
|
22
|
+
- [Installation](#installation)
|
23
|
+
- [Usage](#usage)
|
24
|
+
- [Examples](#examples)
|
25
|
+
- [Filters](#filters)
|
26
|
+
- [Dependencies](#dependencies)
|
27
|
+
- [Documentation](#documentation)
|
28
|
+
- [Extending](#extending)
|
29
|
+
- [3rd Party Extensions](#3rd-party-extensions)
|
30
|
+
- [Instrumenting](#instrumenting)
|
31
|
+
- [Contributing](#contributing)
|
32
|
+
- [Contributors](#contributors)
|
33
|
+
- [Releasing A New Version](#releasing-a-new-version)
|
34
|
+
|
35
|
+
## Installation
|
36
|
+
|
37
|
+
Add this line to your application's Gemfile:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
gem 'html-pipeline'
|
41
|
+
```
|
42
|
+
|
43
|
+
And then execute:
|
44
|
+
|
45
|
+
```sh
|
46
|
+
$ bundle
|
47
|
+
```
|
48
|
+
|
49
|
+
Or install it yourself as:
|
50
|
+
|
51
|
+
```sh
|
52
|
+
$ gem install html-pipeline
|
53
|
+
```
|
54
|
+
|
55
|
+
## Usage
|
56
|
+
|
57
|
+
This library provides a handful of chainable HTML filters to transform user
|
58
|
+
content into markup. A filter takes an HTML string or
|
59
|
+
`Nokogiri::HTML::DocumentFragment`, optionally manipulates it, and then
|
60
|
+
outputs the result.
|
61
|
+
|
62
|
+
For example, to transform Markdown source into Markdown HTML:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
require 'html/pipeline'
|
66
|
+
|
67
|
+
filter = HTML::Pipeline::MarkdownFilter.new("Hi **world**!")
|
68
|
+
filter.call
|
69
|
+
```
|
70
|
+
|
71
|
+
Filters can be combined into a pipeline which causes each filter to hand its
|
72
|
+
output to the next filter's input. So if you wanted to have content be
|
73
|
+
filtered through Markdown and be syntax highlighted, you can create the
|
74
|
+
following pipeline:
|
75
|
+
|
76
|
+
```ruby
|
77
|
+
pipeline = HTML::Pipeline.new [
|
78
|
+
HTML::Pipeline::MarkdownFilter,
|
79
|
+
HTML::Pipeline::SyntaxHighlightFilter
|
80
|
+
]
|
81
|
+
result = pipeline.call <<-CODE
|
82
|
+
This is *great*:
|
83
|
+
|
84
|
+
some_code(:first)
|
85
|
+
|
86
|
+
CODE
|
87
|
+
result[:output].to_s
|
88
|
+
```
|
89
|
+
|
90
|
+
Prints:
|
91
|
+
|
92
|
+
```html
|
93
|
+
<p>This is <em>great</em>:</p>
|
94
|
+
|
95
|
+
<pre><code>some_code(:first)
|
96
|
+
</code></pre>
|
97
|
+
```
|
98
|
+
|
99
|
+
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`.
|
100
|
+
|
101
|
+
Some filters take an optional **context** and/or **result** hash. These are
|
102
|
+
used to pass around arguments and metadata between filters in a pipeline. For
|
103
|
+
example, if you don't want to use GitHub formatted Markdown, you can pass an
|
104
|
+
option in the context hash:
|
105
|
+
|
106
|
+
```ruby
|
107
|
+
filter = HTML::Pipeline::MarkdownFilter.new("Hi **world**!", :gfm => false)
|
108
|
+
filter.call
|
109
|
+
```
|
110
|
+
|
111
|
+
### Examples
|
112
|
+
|
113
|
+
We define different pipelines for different parts of our app. Here are a few
|
114
|
+
paraphrased snippets to get you started:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
# The context hash is how you pass options between different filters.
|
118
|
+
# See individual filter source for explanation of options.
|
119
|
+
context = {
|
120
|
+
:asset_root => "http://your-domain.com/where/your/images/live/icons",
|
121
|
+
:base_url => "http://your-domain.com"
|
122
|
+
}
|
123
|
+
|
124
|
+
# Pipeline providing sanitization and image hijacking but no mention
|
125
|
+
# related features.
|
126
|
+
SimplePipeline = Pipeline.new [
|
127
|
+
SanitizationFilter,
|
128
|
+
TableOfContentsFilter, # add 'name' anchors to all headers and generate toc list
|
129
|
+
CamoFilter,
|
130
|
+
ImageMaxWidthFilter,
|
131
|
+
SyntaxHighlightFilter,
|
132
|
+
EmojiFilter,
|
133
|
+
AutolinkFilter
|
134
|
+
], context
|
135
|
+
|
136
|
+
# Pipeline used for user provided content on the web
|
137
|
+
MarkdownPipeline = Pipeline.new [
|
138
|
+
MarkdownFilter,
|
139
|
+
SanitizationFilter,
|
140
|
+
CamoFilter,
|
141
|
+
ImageMaxWidthFilter,
|
142
|
+
HttpsFilter,
|
143
|
+
MentionFilter,
|
144
|
+
EmojiFilter,
|
145
|
+
SyntaxHighlightFilter
|
146
|
+
], context.merge(:gfm => true) # enable github formatted markdown
|
147
|
+
|
148
|
+
|
149
|
+
# Define a pipeline based on another pipeline's filters
|
150
|
+
NonGFMMarkdownPipeline = Pipeline.new(MarkdownPipeline.filters,
|
151
|
+
context.merge(:gfm => false))
|
152
|
+
|
153
|
+
# Pipelines aren't limited to the web. You can use them for email
|
154
|
+
# processing also.
|
155
|
+
HtmlEmailPipeline = Pipeline.new [
|
156
|
+
PlainTextInputFilter,
|
157
|
+
ImageMaxWidthFilter
|
158
|
+
], {}
|
159
|
+
|
160
|
+
# Just emoji.
|
161
|
+
EmojiPipeline = Pipeline.new [
|
162
|
+
PlainTextInputFilter,
|
163
|
+
EmojiFilter
|
164
|
+
], context
|
165
|
+
```
|
166
|
+
|
167
|
+
## Filters
|
168
|
+
|
169
|
+
* `MentionFilter` - replace `@user` mentions with links
|
170
|
+
* `AbsoluteSourceFilter` - replace relative image urls with fully qualified versions
|
171
|
+
* `AutolinkFilter` - auto_linking urls in HTML
|
172
|
+
* `CamoFilter` - replace http image urls with [camo-fied](https://github.com/atmos/camo) https versions
|
173
|
+
* `EmailReplyFilter` - util filter for working with emails
|
174
|
+
* `EmojiFilter` - everyone loves [emoji](http://www.emoji-cheat-sheet.com/)!
|
175
|
+
* `HttpsFilter` - HTML Filter for replacing http github urls with https versions.
|
176
|
+
* `ImageMaxWidthFilter` - link to full size image for large images
|
177
|
+
* `MarkdownFilter` - convert markdown to html
|
178
|
+
* `PlainTextInputFilter` - html escape text and wrap the result in a div
|
179
|
+
* `SanitizationFilter` - whitelist sanitize user markup
|
180
|
+
* `SyntaxHighlightFilter` - code syntax highlighter
|
181
|
+
* `TextileFilter` - convert textile to html
|
182
|
+
* `TableOfContentsFilter` - anchor headings with name attributes and generate Table of Contents html unordered list linking headings
|
183
|
+
|
184
|
+
## Dependencies
|
185
|
+
|
186
|
+
Filter gem dependencies are not bundled; you must bundle the filter's gem
|
187
|
+
dependencies. The below list details filters with dependencies. For example,
|
188
|
+
`SyntaxHighlightFilter` uses [rouge](https://github.com/jneen/rouge)
|
189
|
+
to detect and highlight languages. For example, to use the `SyntaxHighlightFilter`,
|
190
|
+
add the following to your Gemfile:
|
191
|
+
|
192
|
+
```ruby
|
193
|
+
gem 'rouge'
|
194
|
+
```
|
195
|
+
|
196
|
+
* `AutolinkFilter` - `rinku`
|
197
|
+
* `EmailReplyFilter` - `escape_utils`, `email_reply_parser`
|
198
|
+
* `EmojiFilter` - `gemoji`
|
199
|
+
* `MarkdownFilter` - `commonmarker`
|
200
|
+
* `PlainTextInputFilter` - `escape_utils`
|
201
|
+
* `SanitizationFilter` - `sanitize`
|
202
|
+
* `SyntaxHighlightFilter` - `rouge`
|
203
|
+
* `TableOfContentsFilter` - `escape_utils`
|
204
|
+
* `TextileFilter` - `RedCloth`
|
205
|
+
|
206
|
+
_Note:_ See [Gemfile](/Gemfile) `:test` block for version requirements.
|
207
|
+
|
208
|
+
## Documentation
|
209
|
+
|
210
|
+
Full reference documentation can be [found here](http://rubydoc.info/gems/html-pipeline/frames).
|
211
|
+
|
212
|
+
## Extending
|
213
|
+
To write a custom filter, you need a class with a `call` method that inherits
|
214
|
+
from `HTML::Pipeline::Filter`.
|
215
|
+
|
216
|
+
For example this filter adds a base url to images that are root relative:
|
217
|
+
|
218
|
+
```ruby
|
219
|
+
require 'uri'
|
220
|
+
|
221
|
+
class RootRelativeFilter < HTML::Pipeline::Filter
|
222
|
+
|
223
|
+
def call
|
224
|
+
doc.search("img").each do |img|
|
225
|
+
next if img['src'].nil?
|
226
|
+
src = img['src'].strip
|
227
|
+
if src.start_with? '/'
|
228
|
+
img["src"] = URI.join(context[:base_url], src).to_s
|
229
|
+
end
|
230
|
+
end
|
231
|
+
doc
|
232
|
+
end
|
233
|
+
|
234
|
+
end
|
235
|
+
```
|
236
|
+
|
237
|
+
Now this filter can be used in a pipeline:
|
238
|
+
|
239
|
+
```ruby
|
240
|
+
Pipeline.new [ RootRelativeFilter ], { :base_url => 'http://somehost.com' }
|
241
|
+
```
|
242
|
+
|
243
|
+
### 3rd Party Extensions
|
244
|
+
|
245
|
+
If you have an idea for a filter, propose it as
|
246
|
+
[an issue](https://github.com/jch/html-pipeline/issues) first. This allows us discuss
|
247
|
+
whether the filter is a common enough use case to belong in this gem, or should be
|
248
|
+
built as an external gem.
|
249
|
+
|
250
|
+
Here are some extensions people have built:
|
251
|
+
|
252
|
+
* [html-pipeline-asciidoc_filter](https://github.com/asciidoctor/html-pipeline-asciidoc_filter)
|
253
|
+
* [jekyll-html-pipeline](https://github.com/gjtorikian/jekyll-html-pipeline)
|
254
|
+
* [nanoc-html-pipeline](https://github.com/burnto/nanoc-html-pipeline)
|
255
|
+
* [html-pipeline-bitly](https://github.com/dewski/html-pipeline-bitly)
|
256
|
+
* [html-pipeline-cite](https://github.com/lifted-studios/html-pipeline-cite)
|
257
|
+
* [tilt-html-pipeline](https://github.com/bradgessler/tilt-html-pipeline)
|
258
|
+
* [html-pipeline-wiki-link'](https://github.com/lifted-studios/html-pipeline-wiki-link) - WikiMedia-style wiki links
|
259
|
+
* [task_list](https://github.com/github/task_list) - GitHub flavor Markdown Task List
|
260
|
+
* [html-pipeline-nico_link](https://github.com/rutan/html-pipeline-nico_link) - An HTML::Pipeline filter for [niconico](http://www.nicovideo.jp) description links
|
261
|
+
* [html-pipeline-gitlab](https://gitlab.com/gitlab-org/html-pipeline-gitlab) - This gem implements various filters for html-pipeline used by GitLab
|
262
|
+
* [html-pipeline-youtube](https://github.com/st0012/html-pipeline-youtube) - An HTML::Pipeline filter for YouTube links
|
263
|
+
* [html-pipeline-flickr](https://github.com/st0012/html-pipeline-flickr) - An HTML::Pipeline filter for Flickr links
|
264
|
+
* [html-pipeline-vimeo](https://github.com/dlackty/html-pipeline-vimeo) - An HTML::Pipeline filter for Vimeo links
|
265
|
+
* [html-pipeline-hashtag](https://github.com/mr-dxdy/html-pipeline-hashtag) - An HTML::Pipeline filter for hashtags
|
266
|
+
* [html-pipeline-linkify_github](https://github.com/jollygoodcode/html-pipeline-linkify_github) - An HTML::Pipeline filter to autolink GitHub urls
|
267
|
+
* [html-pipeline-redcarpet_filter](https://github.com/bmikol/html-pipeline-redcarpet_filter) - Render Markdown source text into Markdown HTML using Redcarpet
|
268
|
+
* [html-pipeline-typogruby_filter](https://github.com/bmikol/html-pipeline-typogruby_filter) - Add Typogruby text filters to your HTML::Pipeline
|
269
|
+
* [korgi](https://github.com/jodeci/korgi) - HTML::Pipeline filters for links to Rails resources
|
270
|
+
|
271
|
+
|
272
|
+
## Instrumenting
|
273
|
+
|
274
|
+
Filters and Pipelines can be set up to be instrumented when called. The pipeline
|
275
|
+
must be setup with an
|
276
|
+
[ActiveSupport::Notifications](http://api.rubyonrails.org/classes/ActiveSupport/Notifications.html)
|
277
|
+
compatible service object and a name. New pipeline objects will default to the
|
278
|
+
`HTML::Pipeline.default_instrumentation_service` object.
|
279
|
+
|
280
|
+
``` ruby
|
281
|
+
# the AS::Notifications-compatible service object
|
282
|
+
service = ActiveSupport::Notifications
|
283
|
+
|
284
|
+
# instrument a specific pipeline
|
285
|
+
pipeline = HTML::Pipeline.new [MarkdownFilter], context
|
286
|
+
pipeline.setup_instrumentation "MarkdownPipeline", service
|
287
|
+
|
288
|
+
# or set default instrumentation service for all new pipelines
|
289
|
+
HTML::Pipeline.default_instrumentation_service = service
|
290
|
+
pipeline = HTML::Pipeline.new [MarkdownFilter], context
|
291
|
+
pipeline.setup_instrumentation "MarkdownPipeline"
|
292
|
+
```
|
293
|
+
|
294
|
+
Filters are instrumented when they are run through the pipeline. A
|
295
|
+
`call_filter.html_pipeline` event is published once the filter finishes. The
|
296
|
+
`payload` should include the `filter` name. Each filter will trigger its own
|
297
|
+
instrumentation call.
|
298
|
+
|
299
|
+
``` ruby
|
300
|
+
service.subscribe "call_filter.html_pipeline" do |event, start, ending, transaction_id, payload|
|
301
|
+
payload[:pipeline] #=> "MarkdownPipeline", set with `setup_instrumentation`
|
302
|
+
payload[:filter] #=> "MarkdownFilter"
|
303
|
+
payload[:context] #=> context Hash
|
304
|
+
payload[:result] #=> instance of result class
|
305
|
+
payload[:result][:output] #=> output HTML String or Nokogiri::DocumentFragment
|
306
|
+
end
|
307
|
+
```
|
308
|
+
|
309
|
+
The full pipeline is also instrumented:
|
310
|
+
|
311
|
+
``` ruby
|
312
|
+
service.subscribe "call_pipeline.html_pipeline" do |event, start, ending, transaction_id, payload|
|
313
|
+
payload[:pipeline] #=> "MarkdownPipeline", set with `setup_instrumentation`
|
314
|
+
payload[:filters] #=> ["MarkdownFilter"]
|
315
|
+
payload[:doc] #=> HTML String or Nokogiri::DocumentFragment
|
316
|
+
payload[:context] #=> context Hash
|
317
|
+
payload[:result] #=> instance of result class
|
318
|
+
payload[:result][:output] #=> output HTML String or Nokogiri::DocumentFragment
|
319
|
+
end
|
320
|
+
```
|
321
|
+
|
322
|
+
## FAQ
|
323
|
+
|
324
|
+
### 1. Why doesn't my pipeline work when there's no root element in the document?
|
325
|
+
|
326
|
+
To make a pipeline work on a plain text document, put the `PlainTextInputFilter`
|
327
|
+
at the beginning of your pipeline. This will wrap the content in a `div` so the
|
328
|
+
filters have a root element to work with. If you're passing in an HTML fragment,
|
329
|
+
but it doesn't have a root element, you can wrap the content in a `div`
|
330
|
+
yourself. For example:
|
331
|
+
|
332
|
+
```ruby
|
333
|
+
EmojiPipeline = Pipeline.new [
|
334
|
+
PlainTextInputFilter, # <- Wraps input in a div and escapes html tags
|
335
|
+
EmojiFilter
|
336
|
+
], context
|
337
|
+
|
338
|
+
plain_text = "Gutentag! :wave:"
|
339
|
+
EmojiPipeline.call(plain_text)
|
340
|
+
|
341
|
+
html_fragment = "This is outside of an html element, but <strong>this isn't. :+1:</strong>"
|
342
|
+
EmojiPipeline.call("<div>#{html_fragment}</div>") # <- Wrap your own html fragments to avoid escaping
|
343
|
+
```
|
344
|
+
|
345
|
+
### 2. How do I customize a whitelist for `SanitizationFilter`s?
|
346
|
+
|
347
|
+
`SanitizationFilter::WHITELIST` is the default whitelist used if no `:whitelist`
|
348
|
+
argument is given in the context. The default is a good starting template for
|
349
|
+
you to add additional elements. You can either modify the constant's value, or
|
350
|
+
re-define your own constant and pass that in via the context.
|
351
|
+
|
352
|
+
## Contributing
|
353
|
+
|
354
|
+
Please review the [Contributing Guide](https://github.com/jch/html-pipeline/blob/master/CONTRIBUTING.md).
|
355
|
+
|
356
|
+
1. [Fork it](https://help.github.com/articles/fork-a-repo)
|
357
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
358
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
359
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
360
|
+
5. Create new [Pull Request](https://help.github.com/articles/using-pull-requests)
|
361
|
+
|
362
|
+
To see what has changed in recent versions, see the [CHANGELOG](https://github.com/jch/html-pipeline/blob/master/CHANGELOG.md).
|
363
|
+
|
364
|
+
### Contributors
|
365
|
+
|
366
|
+
Thanks to all of [these contributors](https://github.com/jch/html-pipeline/graphs/contributors).
|
367
|
+
|
368
|
+
Project is a member of the [OSS Manifesto](http://ossmanifesto.org/).
|
369
|
+
|
370
|
+
### Releasing A New Version
|
371
|
+
|
372
|
+
This section is for gem maintainers to cut a new version of the gem.
|
373
|
+
|
374
|
+
* create a new branch named `release-x.y.z` where `x.y.z` follows [semver](http://semver.org)
|
375
|
+
* update lib/html/pipeline/version.rb to next version number X.X.X
|
376
|
+
* update CHANGELOG.md. Prepare a draft with `script/changelog`
|
377
|
+
* push branch and create a new pull request
|
378
|
+
* after tests are green, merge to master
|
379
|
+
* on the master branch, run `script/release`
|
@@ -0,0 +1,14 @@
|
|
1
|
+
unless defined?(Motion::Project::Config)
|
2
|
+
raise "This file must be required within a RubyMotion project Rakefile."
|
3
|
+
end
|
4
|
+
|
5
|
+
require 'motion-cocoapods'
|
6
|
+
|
7
|
+
lib_dir_path = File.dirname(File.expand_path(__FILE__))
|
8
|
+
Motion::Project::App.setup do |app|
|
9
|
+
app.files.unshift(Dir.glob(File.join(lib_dir_path, "motion-html-pipeline/**/*.rb")))
|
10
|
+
|
11
|
+
app.pods do
|
12
|
+
pod 'HTMLKit', '~> 2.1'
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# Gives a Nokogiri type of interface, but uses
|
2
|
+
# HTMLKit (https://github.com/iabudiab/HTMLKit)
|
3
|
+
#
|
4
|
+
# Not meant to duplicate Nokogiri completely, just add a similar interface
|
5
|
+
#------------------------------------------------------------------------------
|
6
|
+
module MotionHTMLPipeline
|
7
|
+
class DocumentFragment < HTMLDocument
|
8
|
+
attr_reader :document
|
9
|
+
|
10
|
+
def self.parse(html)
|
11
|
+
DocumentFragment.new(html)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(html)
|
15
|
+
@document = HTMLDocument.documentWithString(html)
|
16
|
+
end
|
17
|
+
|
18
|
+
def css(query)
|
19
|
+
document.querySelectorAll(query)
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_html
|
23
|
+
document.body.innerHTML
|
24
|
+
end
|
25
|
+
alias :to_s :to_html
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
# require 'nokogiri'
|
2
|
+
# require 'active_support/xml_mini/nokogiri' # convert Documents to hashes
|
3
|
+
|
4
|
+
module MotionHTMLPipeline
|
5
|
+
# GitHub HTML processing filters and utilities. This module includes a small
|
6
|
+
# framework for defining DOM based content filters and applying them to user
|
7
|
+
# provided content.
|
8
|
+
#
|
9
|
+
# See MotionHTMLPipeline::Pipeline::Filter for information on building filters.
|
10
|
+
#
|
11
|
+
# Construct a Pipeline for running multiple HTML filters. A pipeline is created once
|
12
|
+
# with one to many filters, and it then can be `call`ed many times over the course
|
13
|
+
# of its lifetime with input.
|
14
|
+
#
|
15
|
+
# filters - Array of Filter objects. Each must respond to call(doc,
|
16
|
+
# context) and return the modified DocumentFragment or a
|
17
|
+
# String containing HTML markup. Filters are performed in the
|
18
|
+
# order provided.
|
19
|
+
# default_context - The default context hash. Values specified here will be merged
|
20
|
+
# into values from the each individual pipeline run. Can NOT be
|
21
|
+
# nil. Default: empty Hash.
|
22
|
+
# result_class - The default Class of the result object for individual
|
23
|
+
# calls. Default: Hash. Protip: Pass in a Struct to get
|
24
|
+
# some semblance of type safety.
|
25
|
+
class Pipeline
|
26
|
+
# Parse a String into a DocumentFragment object. When a DocumentFragment is
|
27
|
+
# provided, return it verbatim.
|
28
|
+
def self.parse(document_or_html)
|
29
|
+
document_or_html ||= ''
|
30
|
+
if document_or_html.is_a?(String)
|
31
|
+
DocumentFragment.parse(document_or_html)
|
32
|
+
else
|
33
|
+
document_or_html
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Public: Returns an Array of Filter objects for this Pipeline.
|
38
|
+
attr_reader :filters
|
39
|
+
|
40
|
+
# Public: Instrumentation service for the pipeline.
|
41
|
+
# Set an ActiveSupport::Notifications compatible object to enable.
|
42
|
+
attr_accessor :instrumentation_service
|
43
|
+
|
44
|
+
# Public: String name for this Pipeline. Defaults to Class name.
|
45
|
+
attr_writer :instrumentation_name
|
46
|
+
def instrumentation_name
|
47
|
+
return @instrumentation_name if defined?(@instrumentation_name)
|
48
|
+
@instrumentation_name = self.class.name
|
49
|
+
end
|
50
|
+
|
51
|
+
class << self
|
52
|
+
# Public: Default instrumentation service for new pipeline objects.
|
53
|
+
attr_accessor :default_instrumentation_service
|
54
|
+
end
|
55
|
+
|
56
|
+
def initialize(filters, default_context = {}, result_class = nil)
|
57
|
+
raise ArgumentError, 'default_context cannot be nil' if default_context.nil?
|
58
|
+
@filters = filters.flatten.freeze
|
59
|
+
@default_context = default_context.freeze
|
60
|
+
@result_class = result_class || Hash
|
61
|
+
@instrumentation_service = self.class.default_instrumentation_service
|
62
|
+
end
|
63
|
+
|
64
|
+
# Apply all filters in the pipeline to the given HTML.
|
65
|
+
#
|
66
|
+
# html - A String containing HTML or a DocumentFragment object.
|
67
|
+
# context - The context hash passed to each filter. See the Filter docs
|
68
|
+
# for more info on possible values. This object MUST NOT be modified
|
69
|
+
# in place by filters. Use the Result for passing state back.
|
70
|
+
# result - The result Hash passed to each filter for modification. This
|
71
|
+
# is where Filters store extracted information from the content.
|
72
|
+
#
|
73
|
+
# Returns the result Hash after being filtered by this Pipeline. Contains an
|
74
|
+
# :output key with the DocumentFragment or String HTML markup based on the
|
75
|
+
# output of the last filter in the pipeline.
|
76
|
+
def call(html, context = {}, result = nil)
|
77
|
+
context = @default_context.merge(context)
|
78
|
+
context = context.freeze
|
79
|
+
result ||= @result_class.new
|
80
|
+
payload = default_payload filters: @filters.map(&:name),
|
81
|
+
context: context, result: result
|
82
|
+
instrument 'call_pipeline.html_pipeline', payload do
|
83
|
+
result[:output] =
|
84
|
+
@filters.inject(html) do |doc, filter|
|
85
|
+
perform_filter(filter, doc, context, result)
|
86
|
+
end
|
87
|
+
end
|
88
|
+
result
|
89
|
+
end
|
90
|
+
|
91
|
+
# Internal: Applies a specific filter to the supplied doc.
|
92
|
+
#
|
93
|
+
# The filter is instrumented.
|
94
|
+
#
|
95
|
+
# Returns the result of the filter.
|
96
|
+
def perform_filter(filter, doc, context, result)
|
97
|
+
payload = default_payload filter: filter.name,
|
98
|
+
context: context, result: result
|
99
|
+
instrument 'call_filter.html_pipeline', payload do
|
100
|
+
filter.call(doc, context, result)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
# Like call but guarantee the value returned is a DocumentFragment.
|
105
|
+
# Pipelines may return a DocumentFragment or a String. Callers that need a
|
106
|
+
# DocumentFragment should use this method.
|
107
|
+
def to_document(input, context = {}, result = nil)
|
108
|
+
result = call(input, context, result)
|
109
|
+
MotionHTMLPipeline::Pipeline.parse(result[:output])
|
110
|
+
end
|
111
|
+
|
112
|
+
# Like call but guarantee the value returned is a string of HTML markup.
|
113
|
+
def to_html(input, context = {}, result = nil)
|
114
|
+
result = call(input, context, result = nil)
|
115
|
+
output = result[:output]
|
116
|
+
if output.respond_to?(:to_html)
|
117
|
+
output.to_html
|
118
|
+
else
|
119
|
+
output.to_s
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Public: setup instrumentation for this pipeline.
|
124
|
+
#
|
125
|
+
# Returns nothing.
|
126
|
+
def setup_instrumentation(name = nil, service = nil)
|
127
|
+
self.instrumentation_name = name
|
128
|
+
self.instrumentation_service =
|
129
|
+
service || self.class.default_instrumentation_service
|
130
|
+
end
|
131
|
+
|
132
|
+
# Internal: if the `instrumentation_service` object is set, instruments the
|
133
|
+
# block, otherwise the block is ran without instrumentation.
|
134
|
+
#
|
135
|
+
# Returns the result of the provided block.
|
136
|
+
def instrument(event, payload = nil)
|
137
|
+
payload ||= default_payload
|
138
|
+
return yield(payload) unless instrumentation_service
|
139
|
+
instrumentation_service.instrument event, payload do |payload|
|
140
|
+
yield payload
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# Internal: Default payload for instrumentation.
|
145
|
+
#
|
146
|
+
# Accepts a Hash of additional payload data to be merged.
|
147
|
+
#
|
148
|
+
# Returns a Hash.
|
149
|
+
def default_payload(payload = {})
|
150
|
+
{ pipeline: instrumentation_name }.merge(payload)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|