motion-html-pipeline 0.1

Sign up to get free protection for your applications and to get access to all the features.
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