content-pipeline 0.0.1

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