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.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +379 -0
  3. data/lib/motion-html-pipeline.rb +14 -0
  4. data/lib/motion-html-pipeline/document_fragment.rb +27 -0
  5. data/lib/motion-html-pipeline/pipeline.rb +153 -0
  6. data/lib/motion-html-pipeline/pipeline/absolute_source_filter.rb +45 -0
  7. data/lib/motion-html-pipeline/pipeline/body_content.rb +42 -0
  8. data/lib/motion-html-pipeline/pipeline/disabled/@mention_filter.rb +140 -0
  9. data/lib/motion-html-pipeline/pipeline/disabled/autolink_filter.rb +27 -0
  10. data/lib/motion-html-pipeline/pipeline/disabled/camo_filter.rb +93 -0
  11. data/lib/motion-html-pipeline/pipeline/disabled/email_reply_filter.rb +66 -0
  12. data/lib/motion-html-pipeline/pipeline/disabled/emoji_filter.rb +125 -0
  13. data/lib/motion-html-pipeline/pipeline/disabled/markdown_filter.rb +37 -0
  14. data/lib/motion-html-pipeline/pipeline/disabled/plain_text_input_filter.rb +13 -0
  15. data/lib/motion-html-pipeline/pipeline/disabled/sanitization_filter.rb +137 -0
  16. data/lib/motion-html-pipeline/pipeline/disabled/syntax_highlight_filter.rb +44 -0
  17. data/lib/motion-html-pipeline/pipeline/disabled/toc_filter.rb +67 -0
  18. data/lib/motion-html-pipeline/pipeline/filter.rb +163 -0
  19. data/lib/motion-html-pipeline/pipeline/https_filter.rb +27 -0
  20. data/lib/motion-html-pipeline/pipeline/image_filter.rb +17 -0
  21. data/lib/motion-html-pipeline/pipeline/image_max_width_filter.rb +37 -0
  22. data/lib/motion-html-pipeline/pipeline/text_filter.rb +14 -0
  23. data/lib/motion-html-pipeline/pipeline/version.rb +5 -0
  24. data/spec/motion-html-pipeline/_helpers/mock_instumentation_service.rb +19 -0
  25. data/spec/motion-html-pipeline/pipeline/absolute_source_filter_spec.rb +47 -0
  26. data/spec/motion-html-pipeline/pipeline/disabled/auto_link_filter_spec.rb +33 -0
  27. data/spec/motion-html-pipeline/pipeline/disabled/camo_filter_spec.rb +75 -0
  28. data/spec/motion-html-pipeline/pipeline/disabled/email_reply_filter_spec.rb +64 -0
  29. data/spec/motion-html-pipeline/pipeline/disabled/emoji_filter_spec.rb +92 -0
  30. data/spec/motion-html-pipeline/pipeline/disabled/markdown_filter_spec.rb +112 -0
  31. data/spec/motion-html-pipeline/pipeline/disabled/plain_text_input_filter_spec.rb +20 -0
  32. data/spec/motion-html-pipeline/pipeline/disabled/sanitization_filter_spec.rb +164 -0
  33. data/spec/motion-html-pipeline/pipeline/disabled/syntax_highlighting_filter_spec.rb +59 -0
  34. data/spec/motion-html-pipeline/pipeline/disabled/toc_filter_spec.rb +137 -0
  35. data/spec/motion-html-pipeline/pipeline/https_filter_spec.rb +52 -0
  36. data/spec/motion-html-pipeline/pipeline/image_filter_spec.rb +37 -0
  37. data/spec/motion-html-pipeline/pipeline/image_max_width_filter_spec.rb +57 -0
  38. data/spec/motion-html-pipeline/pipeline_spec.rb +80 -0
  39. data/spec/spec_helper.rb +48 -0
  40. 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
+ [![Gem Version](https://badge.fury.io/rb/motion-html-pipeline.svg)](http://badge.fury.io/rb/motion-html-pipeline)
4
+ [![Build Status](https://travis-ci.org/digitalmoksha/motion-html-pipeline.svg?branch=master)](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