content-pipeline 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 63e843633684063415b4e1b255c2cb31951eef56
4
+ data.tar.gz: 77a10f7cb27a40367be4713dbaad5f7c7a676aa3
5
+ SHA512:
6
+ metadata.gz: cfd257ad678d8a45a1e4f939c7b586f050958614a8e8d5161bd65840eb6ce99fa0c901771a8d16efbbe5f12b93c59444c1837377b62c54bcfd32a53cb66603aa
7
+ data.tar.gz: ab38717b3c52a5174c07ea42ab6c40bb391643b21cc8d278427ebe8b3b4b0cd6c416d2d5ac3e7d48c63bd93b0167b168db2256a1a4e6db347ce6e28079b93812
data/Gemfile ADDED
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ group :development do
5
+ gem 'kramdown', '~> 1.1.0'
6
+ gem 'pry', '~> 0.9.12'
7
+
8
+ unless RbConfig::CONFIG['ruby_install_name'] == 'jruby'
9
+ gem 'pygments.rb', '~> 0.5.1'
10
+ gem 'github-markdown', '~> 0.5.3'
11
+ end
12
+ end
data/License ADDED
@@ -0,0 +1,11 @@
1
+ Copyright 2013 Jordon Bedwell
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License"); you may not
4
+ use this file except in compliance with the License. You may obtain a copy of
5
+ the License at: http://www.apache.org/licenses/LICENSE-2.0
6
+
7
+ Unless required by applicable law or agreed to in writing, software
8
+ distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9
+ WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10
+ License for the specific language governing permissions and limitations under
11
+ the License.
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require 'rspec/core/rake_task'
2
+ task :default => :spec
3
+ RSpec::Core::RakeTask.new :spec
data/Readme.md ADDED
@@ -0,0 +1,6 @@
1
+ # Content Pipeline.
2
+
3
+ [![Build Status](https://travis-ci.org/envygeeks/content-pipeline.png?branch=master)](https://travis-ci.org/envygeeks/content-pipeline) [![Coverage Status](https://coveralls.io/repos/envygeeks/content-pipeline/badge.png)](https://coveralls.io/r/envygeeks/content-pipeline) [![Code Climate](https://codeclimate.com/github/envygeeks/content-pipeline.png)](https://codeclimate.com/github/envygeeks/content-pipeline) [![Dependency Status](https://gemnasium.com/envygeeks/content-pipeline.png)](https://gemnasium.com/envygeeks/content-pipeline)
4
+
5
+
6
+ Content pipeline is like `html-pipeline` except it's less restrictive.
@@ -0,0 +1,53 @@
1
+ require_relative 'pipeline/core_extensions/object_ext'
2
+ require_relative 'pipeline/core_extensions/hash_ext'
3
+ $:.unshift(File.expand_path('../../', __FILE__))
4
+
5
+ module Content
6
+
7
+ # --------------------------------------------------------------------------
8
+ # Content pipeline is a class that runs content through a pipeline of user
9
+ # set and/or defined filters returning the final result. It can be mixed
10
+ # and matched anyway and filters can even be skipped and removed.
11
+ # --------------------------------------------------------------------------
12
+
13
+ class Pipeline
14
+ require_relative 'pipeline/filters'
15
+ attr_reader :filters, :opts
16
+
17
+ # ------------------------------------------------------------------------
18
+ # Allows the user to initialize with a custom set of filters or to auto
19
+ # load our filters and use them, the base filters we provider are
20
+ # redcarpet and pygments.
21
+ #
22
+ # @opt filters [Array] a list of filters to use.
23
+ # ------------------------------------------------------------------------
24
+
25
+ def initialize(filters = Filters::DEFAULT_FILTERS, opts = nil)
26
+ @opts, @filters = (opts || {}).freeze, [ filters ].flatten.freeze
27
+ end
28
+
29
+ # ------------------------------------------------------------------------
30
+ # Runs through each of the extensions chosen by the user and then calls
31
+ # them, returning the final result as the final HTML string.
32
+ # ------------------------------------------------------------------------
33
+
34
+ def filter(content, opts = nil)
35
+ opts = @opts.deep_merge(opts || {})
36
+ @filters.each do |filter|
37
+ content = filter.new(content, opts[to_opt(filter)]).run
38
+ end
39
+
40
+ content
41
+ end
42
+
43
+ # ------------------------------------------------------------------------
44
+ # Convert a class and it's name into an opt by splitting up it's name
45
+ # and downcasing the last part returning it as a symbol for opts.
46
+ # ------------------------------------------------------------------------
47
+
48
+ private
49
+ def to_opt(cls)
50
+ cls.name.split(/::/).last.downcase.to_sym
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,21 @@
1
+ module CoreExtensions
2
+
3
+ # --------------------------------------------------------------------------
4
+ # Hash Extensions.
5
+ # --------------------------------------------------------------------------
6
+
7
+ module HashExt
8
+
9
+ # ------------------------------------------------------------------------
10
+ # Merge a hash, merging hashes in hashes with hashes in hashes if hashes.
11
+ # ------------------------------------------------------------------------
12
+
13
+ def deep_merge(new_hash)
14
+ merge(new_hash) do |k, o, n|
15
+ Hash === o && Hash === n ? o.deep_merge(n) : n
16
+ end
17
+ end
18
+ end
19
+ end
20
+
21
+ Hash.send(:include, CoreExtensions::HashExt)
@@ -0,0 +1,43 @@
1
+ require 'nokogiri'
2
+
3
+ module CoreExtensions
4
+
5
+ # --------------------------------------------------------------------------
6
+ # Object Extensions.
7
+ # --------------------------------------------------------------------------
8
+
9
+ module ObjectExt
10
+
11
+ def jruby?
12
+ RbConfig::CONFIG['ruby_install_name'] == 'jruby'
13
+ end
14
+
15
+ # ------------------------------------------------------------------------
16
+ # Convert an element to a Nokogiri document fragment.
17
+ # ------------------------------------------------------------------------
18
+
19
+ def to_nokogiri_fragment
20
+ return self if Nokogiri::HTML::DocumentFragment === self
21
+ Nokogiri::HTML.fragment(respond_to?(:to_html) ? to_html : to_s)
22
+ end
23
+
24
+ # ------------------------------------------------------------------------
25
+ # Require a file with a fallback and if all else fails send an error.
26
+ # ------------------------------------------------------------------------
27
+
28
+ def require_or_fail(a, b, msg)
29
+ require a
30
+ rescue LoadError
31
+ begin
32
+ require b
33
+ rescue LoadError => error
34
+ # Try to keep the call chain proper.
35
+ raise LoadError, msg, error.backtrace[3...-1]
36
+ end
37
+ end
38
+ end
39
+ end
40
+
41
+ # ----------------------------------------------------------------------------
42
+
43
+ Object.send(:include, CoreExtensions::ObjectExt)
@@ -0,0 +1,60 @@
1
+ class Content::Pipeline
2
+
3
+ # --------------------------------------------------------------------------
4
+ # Pipeline is an "abstract" class meant to be inherited from in a defined
5
+ # filter. It provides a set of base methods, namely initialize which
6
+ # spawns the str not to mention add_filter which allows the filter author
7
+ # to define a set of methods that need to be run on this filter.
8
+ # --------------------------------------------------------------------------
9
+
10
+ class Filter
11
+
12
+ # ------------------------------------------------------------------------
13
+ # Initialize.
14
+ # ------------------------------------------------------------------------
15
+
16
+ def initialize(str, opts = nil)
17
+ @opts, @str = (opts || {}), str
18
+ end
19
+
20
+ # ------------------------------------------------------------------------
21
+ # Run the set of filters the user wants ran on the filter.
22
+ # ------------------------------------------------------------------------
23
+
24
+ def run
25
+ self.class.filters.each do |f|
26
+ send(f)
27
+ end
28
+
29
+ @str.to_s
30
+ end
31
+
32
+ class << self
33
+ attr_reader :filters
34
+
35
+ # ----------------------------------------------------------------------
36
+ # Allows the author of a filter to set a method to be run on this,
37
+ # filter, without us having to enforce a specific type of name.
38
+ # ----------------------------------------------------------------------
39
+
40
+ def add_filter(*filters)
41
+ @filters ||= []
42
+ @filters.push(*filters.flatten)
43
+ end
44
+ end
45
+ end
46
+
47
+ # --------------------------------------------------------------------------
48
+
49
+ module Filters
50
+ require_relative 'filters/code_highlight'
51
+ require_relative 'filters/markdown'
52
+
53
+ # ------------------------------------------------------------------------
54
+ # A set of default filters that we use if the user does not supply their
55
+ # own filters for us to use.
56
+ # ------------------------------------------------------------------------
57
+
58
+ DEFAULT_FILTERS = [ Markdown, CodeHighlight ].freeze
59
+ end
60
+ end
@@ -0,0 +1,71 @@
1
+ require 'pygments' unless jruby?
2
+
3
+ # ----------------------------------------------------------------------------
4
+ # A filter that discovers pre tags and then syntax highlights them, also
5
+ # allowing for fallback to just wrapping them so they stay consistent.
6
+ # ----------------------------------------------------------------------------
7
+
8
+ class Content::Pipeline::Filters::CodeHighlight < Content::Pipeline::Filter
9
+ add_filter :highlight
10
+
11
+ Matcher = /<pre>(.+)<\/pre>/m
12
+ Templates = {
13
+ :numb => %Q{<span class="line-number">%s</span>\n},
14
+ :line => '<span class="line">%s</span>',
15
+ :wrap => <<-HTML
16
+ <figure class="code">
17
+ <div class="highlight">
18
+ <table>
19
+ <tbody>
20
+ <tr>
21
+ <td class="gutter">
22
+ <pre>%s</pre>
23
+ </td>
24
+ <td class="code">
25
+ <pre><code class="%s">%s</code></pre>
26
+ </td>
27
+ </tr>
28
+ </tbody>
29
+ </table>
30
+ </div>
31
+ </figure>
32
+ HTML
33
+ }
34
+
35
+ # --------------------------------------------------------------------------
36
+ # Searches for elements we want to transform and transforms them.
37
+ # --------------------------------------------------------------------------
38
+
39
+ private
40
+ def highlight
41
+ @str = @str.to_nokogiri_fragment
42
+ @str.search('pre').each do |node|
43
+ node.replace Templates[:wrap] %
44
+ wrap(pygments(node.inner_text, node[:lang]), node[:lang] || 'text')
45
+ end
46
+ end
47
+
48
+ # --------------------------------------------------------------------------
49
+ # Goes through each line and wraps it as well as creates line numbers.
50
+ # --------------------------------------------------------------------------
51
+
52
+ private
53
+ def wrap(str, lang)
54
+ lines, numbs = '', ''; str.each_line.with_index(1) do |line, numb|
55
+ lines+= Templates[:line] % line
56
+ numbs+= Templates[:numb] % numb
57
+ end
58
+
59
+ [numbs, lang, lines]
60
+ end
61
+
62
+ # --------------------------------------------------------------------------
63
+ # Wraps around Pygments catching a timeout error so that it can cont.
64
+ # --------------------------------------------------------------------------
65
+
66
+ private
67
+ def pygments(str, lang)
68
+ return str if jruby? || !Pygments::Lexer[lang]
69
+ Pygments::Lexer[lang].highlight(str) =~ Matcher ? $1 : str
70
+ end
71
+ end
@@ -0,0 +1,89 @@
1
+ # ----------------------------------------------------------------------------
2
+ # A filter that supports Github-Markdown and also has a few filters to strip
3
+ # the most basic unsafe content, if the user chooses this to be done.
4
+ # ----------------------------------------------------------------------------
5
+
6
+ class Content::Pipeline::Filters::Markdown < Content::Pipeline::Filter
7
+ add_filter :markdown, :strip_html
8
+
9
+ # --------------------------------------------------------------------------
10
+ # Parse Markdown content.
11
+ # --------------------------------------------------------------------------
12
+
13
+ private
14
+ def markdown
15
+ type = @opts.fetch(:type, ((jruby?) ? (:kramdown) : (:gfm)))
16
+
17
+ @str = case
18
+ when type =~ /\Amarkdown|gfm\Z/
19
+ require 'github/markdown'
20
+ GitHub::Markdown.to_html(@str, @opts.fetch(:type, :gfm))
21
+ else
22
+ require 'kramdown'
23
+ fix_kramdown_wraps(Kramdown::Document.
24
+ new(convert_backtick(@str), :enable_coderay => false).to_html)
25
+ end
26
+ end
27
+
28
+ # --------------------------------------------------------------------------
29
+ # Discovers private methods that start with strip_ and runs them if the
30
+ # filter is in safe mode. Which will strip certain tags from the data.
31
+ #
32
+ # Doing it this way allows us to allow people to extend this class and add
33
+ # what they wish to it, while us preventing them from monkey patching key
34
+ # methods and having to keep those up-to-date.
35
+ # --------------------------------------------------------------------------
36
+
37
+ private
38
+ def strip_html
39
+ @str = @str.to_nokogiri_fragment
40
+ if @opts[:safe]
41
+ private_methods(false).keep_if { |m| m =~ /\Astrip_/ }.each do |m|
42
+ unless m == :strip_html
43
+ send(m)
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ # --------------------------------------------------------------------------
50
+ # Strip anchor tags.
51
+ # --------------------------------------------------------------------------
52
+
53
+ private
54
+ def strip_links
55
+ @str.search('a').each do |node|
56
+ node.replace(node[:href])
57
+ end
58
+ end
59
+
60
+ # --------------------------------------------------------------------------
61
+ # Strip image tags.
62
+ # --------------------------------------------------------------------------
63
+
64
+ private
65
+ def strip_image
66
+ @str.search('img').each do |node|
67
+ # Tries to cover single line images wrapped in a paragraph.
68
+ node.parent.children.count == 1 ? node.parent.remove : node.remove
69
+ end
70
+ end
71
+
72
+ # --------------------------------------------------------------------------
73
+ # Converts Github style backticks over to Portable ~~~.
74
+ # --------------------------------------------------------------------------
75
+
76
+ private
77
+ def convert_backtick(str)
78
+ str.gsub(/^`{3}(\s?[a-zA-Z0-9]+)?$/, '~~~\\1')
79
+ end
80
+
81
+ # --------------------------------------------------------------------------
82
+ # Converts <pre><code class="language-ruby"> to <pre lang="lang">.
83
+ # --------------------------------------------------------------------------
84
+
85
+ private
86
+ def fix_kramdown_wraps(str)
87
+ str.gsub(/<pre><code class="language-([A-Za-z0-9]+)">/, '<pre lang="\\1"><code>')
88
+ end
89
+ end
@@ -0,0 +1,5 @@
1
+ module Content
2
+ class Pipeline
3
+ VERSION = '0.0.1'
4
+ end
5
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: content-pipeline
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Jordon Bedwell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-07-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: nokogiri
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ~>
18
+ - !ruby/object:Gem::Version
19
+ version: 1.6.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ~>
25
+ - !ruby/object:Gem::Version
26
+ version: 1.6.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: coveralls
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 0.6.7
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 0.6.7
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ~>
46
+ - !ruby/object:Gem::Version
47
+ version: 10.1.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 10.1.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - '>='
60
+ - !ruby/object:Gem::Version
61
+ version: 2.14.0.rc1
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - '>='
67
+ - !ruby/object:Gem::Version
68
+ version: 2.14.0.rc1
69
+ - !ruby/object:Gem::Dependency
70
+ name: luna-rspec-formatters
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - '>='
74
+ - !ruby/object:Gem::Version
75
+ version: 0.0.1
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: 0.0.1
83
+ description: A less restrictive version of html-pipeline for content.
84
+ email: envygeeks@gmail.com
85
+ executables: []
86
+ extensions: []
87
+ extra_rdoc_files: []
88
+ files:
89
+ - Readme.md
90
+ - License
91
+ - Rakefile
92
+ - Gemfile
93
+ - lib/content/pipeline/filters/markdown.rb
94
+ - lib/content/pipeline/filters/code_highlight.rb
95
+ - lib/content/pipeline/core_extensions/object_ext.rb
96
+ - lib/content/pipeline/core_extensions/hash_ext.rb
97
+ - lib/content/pipeline/version.rb
98
+ - lib/content/pipeline/filters.rb
99
+ - lib/content/pipeline.rb
100
+ homepage: https://github.com/envygeeks/content-pipeline
101
+ licenses:
102
+ - Apache 2.0
103
+ metadata: {}
104
+ post_install_message:
105
+ rdoc_options: []
106
+ require_paths:
107
+ - lib
108
+ required_ruby_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - '>='
111
+ - !ruby/object:Gem::Version
112
+ version: '0'
113
+ required_rubygems_version: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ requirements: []
119
+ rubyforge_project:
120
+ rubygems_version: 2.0.3
121
+ signing_key:
122
+ specification_version: 4
123
+ summary: Adds a pipeline for your content.
124
+ test_files: []