Skip to content

Commit

Permalink
Filters Manage Dependencies
Browse files Browse the repository at this point in the history
* Add dependency management test cases for each `Filter` with a
  dependency
* Add `assert_dependency_management_error` custom assertion which
  asserts a custom exception and message are raised if a dependency
  is missing
* Move all `Filter` dependencies to `Gemfile` `:test` block for
  test cases and CI
* Implement `TestingDependency` helper to abstract unloading and
  loading `Gemfile` `:test` block gems when asserting dependency
  management errors
* Implement `MissingDependencyException` custom exception with
 `MESSAGE` constant as a format string, so each `Filter` raises a
  uniform exception
* Add `begin..rescue..end` blocks around each `Filter` `require`
  statement to raise a `MissingDependencyException` when a gem can
  not be loaded
* Update README.md detailing new dependency management with listing
  of `Filter` gem dependencies
* Add gemspec post install message to inform users their apps must
  bundle `Filter` dependencies
  • Loading branch information
Simeon F. Willbanks committed Aug 25, 2013
1 parent a731d82 commit d3fc6d9
Show file tree
Hide file tree
Showing 23 changed files with 282 additions and 46 deletions.
21 changes: 18 additions & 3 deletions Gemfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,24 @@
source 'https://rubygems.org'
source "https://rubygems.org"

# Specify your gem's dependencies in html-pipeline.gemspec
gemspec

group :development do
gem 'bundler'
gem 'rake'
gem "bundler"
gem "rake"
end

group :test do
gem "rinku", "~> 1.7", :require => false
gem "gemoji", "~> 1.0", :require => false
gem "RedCloth", "~> 4.2.9", :require => false
gem "escape_utils", "~> 0.3", :require => false
gem "github-linguist", "~> 2.6.2", :require => false
gem "github-markdown", "~> 0.5", :require => false

if RUBY_VERSION < "1.9.2"
gem "sanitize", ">= 2", "< 2.0.4", :require => false
else
gem "sanitize", "~> 2.0", :require => false
end
end
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,18 +98,29 @@ filter.call
* `TextileFilter` - convert textile to html
* `TableOfContentsFilter` - anchor headings with name attributes and generate Table of Contents html unordered list linking headings

## Syntax highlighting
## Dependencies

Filter gem dependencies are not bundled; you must bundle the filter's gem
dependencies. The below list details filters with dependencies. For example,
`SyntaxHighlightFilter` uses [github-linguist](https://github.com/github/linguist)
to detect and highlight languages. It isn't included as a dependency by default
because it's a large dependency and
[a hassle to build on heroku](https://github.com/jch/html-pipeline/issues/33).
To use the filter, add the following to your Gemfile:
to detect and highlight languages. To use the `SyntaxHighlightFilter`,
add the following to your Gemfile:

```ruby
gem 'github-linguist'
```

* `AutolinkFilter` - `rinku`
* `EmailReplyFilter` - `escape_utils`
* `EmojiFilter` - `gemoji`
* `MarkdownFilter` - `github-markdown`
* `PlainTextInputFilter` - `escape_utils`
* `SanitizationFilter` - `sanitize`
* `SyntaxHighlightFilter` - `github-linguist`
* `TextileFilter` - `RedCloth`

_Note:_ See [Gemfile](https://github.com/jch/html-pipeline/blob/master/Gemfile) `:test` block for version requirements.

## Examples

We define different pipelines for different parts of our app. Here are a few
Expand Down
18 changes: 11 additions & 7 deletions html-pipeline.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@ Gem::Specification.new do |gem|
gem.test_files = gem.files.grep(%r{^test})
gem.require_paths = ["lib"]

gem.add_dependency "gemoji", "~> 1.0"
gem.add_dependency "nokogiri", RUBY_VERSION < "1.9.2" ? [">= 1.4", "< 1.6"] : "~> 1.4"
gem.add_dependency "github-markdown", "~> 0.5"
gem.add_dependency "sanitize", RUBY_VERSION < "1.9.2" ? [">= 2", "< 2.0.4"] : "~> 2.0"
gem.add_dependency "rinku", "~> 1.7"
gem.add_dependency "escape_utils", "~> 0.3"
gem.add_dependency "nokogiri", RUBY_VERSION < "1.9.2" ? [">= 1.4", "< 1.6"] : "~> 1.4"

gem.add_development_dependency "activesupport", RUBY_VERSION < "1.9.3" ? [">= 2", "< 4"] : ">= 2"
gem.add_development_dependency "github-linguist", "~> 2.6.2"

gem.post_install_message = <<msg
----------------------------------------------
Thank you for installing html-pipeline!
Filter gem dependencies are no longer bundled.
You must bundle Filter gem dependencies.
See html-pipeline README.md for more details.
https://github.com/jch/html-pipeline
----------------------------------------------
msg
end
1 change: 0 additions & 1 deletion lib/html/pipeline.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
require "nokogiri"
require "active_support/xml_mini/nokogiri" # convert Documents to hashes
require "escape_utils"

module HTML
# GitHub HTML processing filters and utilities. This module includes a small
Expand Down
7 changes: 6 additions & 1 deletion lib/html/pipeline/autolink_filter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
require 'rinku'
begin
require "rinku"
rescue LoadError => e
missing = HTML::Pipeline::Filter::MissingDependencyException
raise missing, missing::MESSAGE % "rinku", e.backtrace
end

module HTML
class Pipeline
Expand Down
9 changes: 8 additions & 1 deletion lib/html/pipeline/email_reply_filter.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
begin
require "escape_utils"
rescue LoadError => e
missing = HTML::Pipeline::Filter::MissingDependencyException
raise missing, missing::MESSAGE % "escape_utils", e.backtrace
end

module HTML
class Pipeline
# HTML Filter that converts email reply text into an HTML DocumentFragment.
Expand Down Expand Up @@ -53,4 +60,4 @@ def call
end
end
end
end
end
9 changes: 7 additions & 2 deletions lib/html/pipeline/emoji_filter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
require 'emoji'
begin
require "gemoji"
rescue LoadError => e
missing = HTML::Pipeline::Filter::MissingDependencyException
raise missing, missing::MESSAGE % "gemoji", e.backtrace
end

module HTML
class Pipeline
Expand Down Expand Up @@ -51,4 +56,4 @@ def asset_root
end
end
end
end
end
17 changes: 17 additions & 0 deletions lib/html/pipeline/filter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ class Pipeline
# Each filter may define additional options and output values. See the class
# docs for more info.
class Filter
# Public: Custom Exception raised when a Filter dependency is not installed.
#
# Examples
#
# begin
# require "rinku"
# rescue LoadError => e
# missing = HTML::Pipeline::Filter::MissingDependencyException
# raise missing, missing::MESSAGE % "rinku", e.backtrace
# end
class MissingDependencyException < StandardError
# Public: Format String for MissingDependencyException message.
MESSAGE = "Missing html-pipeline dependency: " +
"Please add `%s` to your Gemfile; " +
"see html-pipeline Gemfile for version."
end

class InvalidDocumentException < StandardError; end

def initialize(doc, context = nil, result = nil)
Expand Down
9 changes: 7 additions & 2 deletions lib/html/pipeline/markdown_filter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
require 'github/markdown'
begin
require "github/markdown"
rescue LoadError => e
missing = HTML::Pipeline::Filter::MissingDependencyException
raise missing, missing::MESSAGE % "github-markdown", e.backtrace
end

module HTML
class Pipeline
Expand Down Expand Up @@ -26,4 +31,4 @@ def call
end
end
end
end
end
9 changes: 8 additions & 1 deletion lib/html/pipeline/plain_text_input_filter.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
begin
require "escape_utils"
rescue LoadError => e
missing = HTML::Pipeline::Filter::MissingDependencyException
raise missing, missing::MESSAGE % "escape_utils", e.backtrace
end

module HTML
class Pipeline
# Simple filter for plain text input. HTML escapes the text input and wraps it
Expand All @@ -8,4 +15,4 @@ def call
end
end
end
end
end
7 changes: 6 additions & 1 deletion lib/html/pipeline/sanitization_filter.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
require 'sanitize'
begin
require "sanitize"
rescue LoadError => e
missing = HTML::Pipeline::Filter::MissingDependencyException
raise missing, missing::MESSAGE % "sanitize", e.backtrace
end

module HTML
class Pipeline
Expand Down
9 changes: 5 additions & 4 deletions lib/html/pipeline/syntax_highlight_filter.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
begin
require 'linguist'
rescue LoadError
raise LoadError, "You need to install 'github-linguist' before using the SyntaxHighlightFilter. See README.md for details"
require "linguist"
rescue LoadError => e
missing = HTML::Pipeline::Filter::MissingDependencyException
raise missing, missing::MESSAGE % "github-linguist", e.backtrace
end

module HTML
Expand Down Expand Up @@ -30,4 +31,4 @@ def highlight_with_timeout_handling(lexer, text)
end
end
end
end
end
9 changes: 8 additions & 1 deletion lib/html/pipeline/textile_filter.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
begin
require "redcloth"
rescue LoadError => e
missing = HTML::Pipeline::Filter::MissingDependencyException
raise missing, missing::MESSAGE % "RedCloth", e.backtrace
end

module HTML
class Pipeline
# HTML Filter that converts Textile text into HTML and converts into a
Expand All @@ -18,4 +25,4 @@ def call
end
end
end
end
end
92 changes: 92 additions & 0 deletions test/helpers/testing_dependency.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Public: Methods useful for testing Filter dependencies. All methods are class
# methods and should be called on the TestingDependency class.
#
# Examples
#
# TestingDependency.temporarily_remove_dependency_by gem_name do
# exception = assert_raise HTML::Pipeline::Filter::MissingDependencyException do
# load TestingDependency.filter_path_from filter_name
# end
# end
class TestingDependency
# Public: Use to safely test a Filter's gem dependency error handling.
# For a certain gem dependency, remove the gem's loaded paths and features.
# Once these are removed, yield to a block which can assert a specific
# exception. Once the block is finished, add back the gem's paths and
# features to the load path and loaded features, so other tests can assert
# Filter functionality.
#
# gem_name - The String of the gem's name.
# block - Required block which asserts gem dependency error handling.
#
# Examples
#
# TestingDependency.temporarily_remove_dependency_by gem_name do
# exception = assert_raise HTML::Pipeline::Filter::MissingDependencyException do
# load TestingDependency.filter_path_from filter_name
# end
# end
#
# Returns nothing.
def self.temporarily_remove_dependency_by(gem_name, &block)
paths = gem_load_paths_from gem_name
features = gem_loaded_features_from gem_name

$LOAD_PATH.delete_if { |path| paths.include? path }
$LOADED_FEATURES.delete_if { |feature| features.include? feature }

yield

$LOAD_PATH.unshift(*paths)
$LOADED_FEATURES.unshift(*features)
end

# Public: Find a Filter's load path.
#
# gem_name - The String of the gem's name.
#
# Examples
#
# filter_path_from("autolink_filter")
# # => "/Users/simeon/Projects/html-pipeline/test/helpers/../../lib/html/pipeline/autolink_filter.rb"
#
# Returns String of load path.
def self.filter_path_from(filter_name)
File.join(File.dirname(__FILE__), "..", "..", "lib", "html", "pipeline", "#{filter_name}.rb")
end

private
# Internal: Find a gem's load paths.
#
# gem_name - The String of the gem's name.
#
# Examples
#
# gem_load_paths_from("rinku")
# # => ["/Users/simeon/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/rinku-1.7.3/lib"]
#
# Returns Array of load paths.
def self.gem_load_paths_from(gem_name)
$LOAD_PATH.select{ |path| /#{gem_name}/i =~ path }
end

# Internal: Find a gem's loaded features.
#
# gem_name - The String of the gem's name.
#
# Examples
#
# gem_loaded_features_from("rinku")
# # => ["/Users/simeon/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/rinku-1.7.3/lib/rinku.bundle",
# "/Users/simeon/.rbenv/versions/1.9.3-p429/lib/ruby/gems/1.9.1/gems/rinku-1.7.3/lib/rinku.rb"]
#
# Returns Array of loaded features.
def self.gem_loaded_features_from(gem_name)
# gem github-markdown has a feature "github/markdown.rb".
# Replace gem name dashes and underscores with regexp
# range to match all features.
gem_name_regexp = gem_name.split(/[-_]/).join("[\/_-]")

$LOADED_FEATURES.select{ |feature| /#{gem_name_regexp}/i =~ feature }
end
end
4 changes: 4 additions & 0 deletions test/html/pipeline/autolink_filter_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
AutolinkFilter = HTML::Pipeline::AutolinkFilter

class HTML::Pipeline::AutolinkFilterTest < Test::Unit::TestCase
def test_dependency_management
assert_dependency_management_error "autolink_filter", "rinku"
end

def test_uses_rinku_for_autolinking
# just try to parse a complicated piece of HTML
# that Rails auto_link cannot handle
Expand Down
7 changes: 7 additions & 0 deletions test/html/pipeline/email_reply_filter_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
require "test_helper"

class HTML::Pipeline::EmailReplyFilterTest < Test::Unit::TestCase
def test_dependency_management
assert_dependency_management_error "email_reply_filter", "escape_utils"
end
end
14 changes: 9 additions & 5 deletions test/html/pipeline/emoji_filter_test.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
require 'test_helper'
require "test_helper"

class HTML::Pipeline::EmojiFilterTest < Test::Unit::TestCase
EmojiFilter = HTML::Pipeline::EmojiFilter


def test_dependency_management
assert_dependency_management_error "emoji_filter", "gemoji"
end

def test_emojify
filter = EmojiFilter.new("<p>:shipit:</p>", {:asset_root => 'https://foo.com'})
filter = EmojiFilter.new("<p>:shipit:</p>", {:asset_root => "https://foo.com"})
doc = filter.call
assert_match "https://foo.com/emoji/shipit.png", doc.search('img').attr('src').value
assert_match "https://foo.com/emoji/shipit.png", doc.search("img").attr("src").value
end

def test_required_context_validation
Expand All @@ -15,4 +19,4 @@ def test_required_context_validation
}
assert_match /:asset_root/, exception.message
end
end
end
Loading

1 comment on commit d3fc6d9

@wanitaperfect
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Qaseh fitrulqim

Please sign in to comment.