rack-pygmoku 0.1.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.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/CHANGELOG ADDED
@@ -0,0 +1,5 @@
1
+ 0.1.0
2
+ * Initial version; basic implementation
3
+
4
+ 0.1.1
5
+ * Add HTML unescaping to deal with the escaped output from Markdown
data/Gemfile ADDED
@@ -0,0 +1,15 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Gem dependencies
4
+ gem "nokogiri", "~> 1.5.0"
5
+ gem "pygments.rb", "~> 0.2.0"
6
+ gem "rack"
7
+
8
+ # Development dependencies
9
+ group :development do
10
+ gem "bundler", "~> 1.1.0"
11
+ gem "rake", "~> 0.9.0"
12
+ gem "rdoc", "~> 3.12"
13
+ gem "rspec", "~> 2.9.0"
14
+ gem "watchr", "~> 0.7"
15
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,39 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ blankslate (2.1.2.4)
5
+ diff-lcs (1.1.3)
6
+ ffi (1.0.11)
7
+ json (1.6.6)
8
+ nokogiri (1.5.2)
9
+ pygments.rb (0.2.11)
10
+ rubypython (~> 0.5.3)
11
+ rack (1.4.1)
12
+ rake (0.9.2.2)
13
+ rdoc (3.12)
14
+ json (~> 1.4)
15
+ rspec (2.9.0)
16
+ rspec-core (~> 2.9.0)
17
+ rspec-expectations (~> 2.9.0)
18
+ rspec-mocks (~> 2.9.0)
19
+ rspec-core (2.9.0)
20
+ rspec-expectations (2.9.1)
21
+ diff-lcs (~> 1.1.3)
22
+ rspec-mocks (2.9.0)
23
+ rubypython (0.5.3)
24
+ blankslate (>= 2.1.2.3)
25
+ ffi (~> 1.0.7)
26
+ watchr (0.7)
27
+
28
+ PLATFORMS
29
+ ruby
30
+
31
+ DEPENDENCIES
32
+ bundler (~> 1.1.0)
33
+ nokogiri (~> 1.5.0)
34
+ pygments.rb (~> 0.2.0)
35
+ rack
36
+ rake (~> 0.9.0)
37
+ rdoc (~> 3.12)
38
+ rspec (~> 2.9.0)
39
+ watchr (~> 0.7)
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Kevin Rohrbaugh
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,39 @@
1
+ # rack-pygmoku
2
+
3
+ `Rack::Pygmoku` is a middleware for generating code syntax highlighting using the
4
+ [Pygments](http://pygments.org/) library in an environment where you cannot
5
+ install Pygments directly.
6
+
7
+ In other words, it's ideal for use on Heroku.
8
+
9
+ ## Usage
10
+
11
+ First, of course, install the gem.
12
+
13
+ Currently, `rack-pygmoku` only supports Markdown-style code blocks, like
14
+ so:
15
+
16
+ <pre data-lang='ruby'>
17
+ <code>
18
+ def greeting
19
+ 'Hello World!'
20
+ end
21
+ </code>
22
+ </pre>
23
+
24
+ _Note:_ Put the short name of the
25
+ [Pygments lexer](http://pygments.org/docs/lexers/) that you want to use in the
26
+ `data-lang` or `data-lexer` attribute on the `pre` block.
27
+
28
+ ## Status
29
+
30
+ This is mainly a _toy project_ that I put together for my Nesta-powered
31
+ blog, and is likely to be maintained as such unless others find it
32
+ useful.
33
+
34
+ ## Copyright
35
+
36
+ Copyright (c) 2011 Kevin Rohrbaugh.
37
+
38
+ See LICENSE.txt for further details.
39
+
data/Rakefile ADDED
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+
4
+ begin
5
+ Bundler.setup(:default, :development)
6
+ rescue Bundler::BundlerError => e
7
+ $stderr.puts e.message
8
+ $stderr.puts "Run `bundle install` to install missing gems"
9
+ exit e.status_code
10
+ end
11
+
12
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
13
+ require 'rack/pygmoku/version'
14
+
15
+ def pkg_dir; @pkg_dir ||= File.expand_path('../pkg', __FILE__); end
16
+ def gem_name; 'rack-pygmoku'; end
17
+ def gemspec_file_name; "#{gem_name}.gemspec"; end
18
+ def gem_file_name; "#{gem_name}-#{Rack::Pygmoku::Version::STRING}.gem"; end
19
+
20
+ desc "Build gem"
21
+ task :build do
22
+ system "gem build #{gemspec_file_name}"
23
+
24
+ FileUtils.mkdir_p(pkg_dir) unless Dir.exist?(pkg_dir)
25
+ FileUtils.mv gem_file_name, pkg_dir
26
+ end
27
+
28
+ desc "Release gem"
29
+ task :release => :build do
30
+ system "gem push #{File.join(pkg_dir, gem_file_name)}"
31
+ end
32
+
33
+ require 'rspec/core'
34
+ require 'rspec/core/rake_task'
35
+ RSpec::Core::RakeTask.new(:spec) do |spec|
36
+ spec.pattern = FileList['spec/**/*_spec.rb']
37
+ end
38
+
39
+ task :default => :spec
40
+
41
+ require 'rdoc/task'
42
+ RDoc::Task.new do |rdoc|
43
+ rdoc.main = 'README.markdown'
44
+ rdoc.rdoc_files.include('README*', 'lib/**/*.rb')
45
+ end
46
+
47
+ desc "Run watchr (auto-test)"
48
+ task :watchr do
49
+ sh %{bundle exec watchr watchr.rb}
50
+ end
@@ -0,0 +1,72 @@
1
+ require 'rack/utils'
2
+ require 'nokogiri'
3
+
4
+ module Rack
5
+ class Pygmoku
6
+ include Rack::Utils
7
+
8
+ def initialize(app, opts = {})
9
+ @app = app
10
+ @opts = default_opts.merge(opts)
11
+ end
12
+
13
+ def call(env)
14
+ status, headers, response = @app.call(env)
15
+ headers = HeaderHash.new(headers)
16
+
17
+ if highlight?(status, headers)
18
+ content = highlight(response.join)
19
+ headers['Content-Length'] = bytesize(content).to_s
20
+ response = [content]
21
+ end
22
+
23
+ [status, headers, response]
24
+ end
25
+
26
+ private
27
+ def highlight?(status, headers)
28
+ status == 200 &&
29
+ !headers['Transfer-Encoding'] &&
30
+ headers['Content-Type'] =~ /html/
31
+ end
32
+
33
+ def highlight(content)
34
+ require 'pygments'
35
+
36
+ element = @opts[:element]
37
+
38
+ document = Nokogiri::HTML(content, nil, 'utf-8')
39
+ nodes = document.css(element)
40
+ nodes.each do |node|
41
+ parent_node = node.parent
42
+ lexer = get_lexer(parent_node)
43
+ content = unescape_html(node.content)
44
+
45
+ highlighted = Pygments.highlight(content, {:lexer => lexer })
46
+ parent_node.replace(highlighted)
47
+ parent_node.remove()
48
+ end
49
+
50
+ document.serialize
51
+ end
52
+
53
+ def get_lexer(node)
54
+ attribute = @opts[:lexer_attr]
55
+ lexer = 'html'
56
+ lexer = node[attribute] if node.has_attribute?(attribute)
57
+ lexer
58
+ end
59
+
60
+ def unescape_html(html)
61
+ html.to_s.gsub(/&#x000A;/i, "\n").gsub("&lt;", '<').gsub(
62
+ "&gt;", '>').gsub("&amp;", '&')
63
+ end
64
+
65
+ def default_opts
66
+ {
67
+ :element => 'pre>code',
68
+ :lexer_attr => 'data-lang'
69
+ }
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,12 @@
1
+ module Rack
2
+ class Pygmoku
3
+ module Version
4
+ MAJOR = 0
5
+ MINOR = 1
6
+ FIX = 1
7
+ PRE = nil
8
+
9
+ STRING = [MAJOR, MINOR, FIX, PRE].compact.join('.')
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,55 @@
1
+ require 'spec_helper'
2
+ require 'rack/pygmoku/version'
3
+
4
+ describe Rack::Pygmoku::Version do
5
+
6
+ describe "::STRING" do
7
+ it "is defined" do
8
+ defined?(subject::STRING).should be_true
9
+ end
10
+
11
+ it "is a String" do
12
+ subject::STRING.should be_a String
13
+ end
14
+
15
+ it "does not end in ." do
16
+ subject::STRING.end_with?('.').should be_false
17
+ end
18
+ end
19
+
20
+ describe "::MAJOR" do
21
+ it "is defined" do
22
+ defined?(subject::MAJOR).should be_true
23
+ end
24
+
25
+ it "is a Fixnum" do
26
+ subject::MAJOR.should be_a Fixnum
27
+ end
28
+ end
29
+
30
+ describe "::MINOR" do
31
+ it "is defined" do
32
+ defined?(subject::MINOR).should be_true
33
+ end
34
+
35
+ it "is a Fixnum" do
36
+ subject::MINOR.should be_a Fixnum
37
+ end
38
+ end
39
+
40
+ describe "::FIX" do
41
+ it "is defined" do
42
+ defined?(subject::FIX).should be_true
43
+ end
44
+
45
+ it "is a Fixnum" do
46
+ subject::FIX.should be_a Fixnum
47
+ end
48
+ end
49
+
50
+ describe "::PRE" do
51
+ it "is defined" do
52
+ defined?(subject::PRE).should be_true
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,121 @@
1
+ require 'spec_helper'
2
+
3
+ describe Rack::Pygmoku do
4
+ let(:pygmoku) { Rack::Pygmoku.new(app) }
5
+ let(:app) do
6
+ rack_app = double('app').as_null_object
7
+ rack_app.stub(:call).and_return([status, headers, response])
8
+ rack_app
9
+ end
10
+ let(:status) { 500 }
11
+ let(:headers) { { 'Content-Length' => 0, 'Content-Type' => content_type } }
12
+ let(:content_type) { 'text/plain' }
13
+ let(:response) { Array.new }
14
+ let(:env) { Hash.new }
15
+
16
+ #*****************************************************************************
17
+ # Shared example groups
18
+ #*****************************************************************************
19
+ shared_examples_for "the status is immutable" do
20
+ it "does not alter the status" do
21
+ pygmoku.call(env)[0].should == status
22
+ end
23
+ end
24
+
25
+ shared_examples_for "Content-Length is set" do
26
+ include Rack::Utils
27
+
28
+ it "sets the Content-Length" do
29
+ status, headers, response = pygmoku.call(env)
30
+ content_length = bytesize(response.join).to_s
31
+ headers['Content-Length'] = content_length
32
+ end
33
+ end
34
+
35
+ shared_examples_for "pass-through middleware" do
36
+ it_should_behave_like "the status is immutable"
37
+
38
+ it "does not alter the headers" do
39
+ pygmoku.call(env)[1].should == headers
40
+ end
41
+
42
+ it "does not alter the response" do
43
+ pygmoku.call(env)[2].should == response
44
+ end
45
+ end
46
+
47
+ #*****************************************************************************
48
+ # Specs
49
+ #*****************************************************************************
50
+ describe "#call" do
51
+ it "responds to #call" do
52
+ pygmoku.should respond_to :call
53
+ end
54
+
55
+ context "when the response is 200" do
56
+ let(:status) { 200 }
57
+
58
+ context "and the content is HTML" do
59
+ include Specs::HtmlHelper
60
+
61
+ let(:content_type) { 'text/html' }
62
+
63
+ context "and there are matching elements" do
64
+ let(:response) do
65
+ html = html_doc_containing %Q{
66
+ <pre data-lang='ruby'>
67
+ <code>
68
+ def hello
69
+ "Hello World!"
70
+ end
71
+ </code>
72
+ </pre>
73
+ }
74
+ end
75
+ it_should_behave_like "the status is immutable"
76
+ it_should_behave_like "Content-Length is set"
77
+
78
+ it "replaces the highlight element" do
79
+ response = pygmoku.call(env)[2]
80
+ document = Nokogiri::HTML(response.join)
81
+ document.css('pre>code').should be_empty
82
+ end
83
+
84
+ it "inserts marked-up div" do
85
+ response = pygmoku.call(env)[2]
86
+ document = Nokogiri::HTML(response.join)
87
+ document.css('div.highlight').should have(1).element
88
+ end
89
+ end
90
+
91
+ context "and there are no matching elements" do
92
+ let(:response) do
93
+ html = html_doc_containing %Q{
94
+ <h1>No Code Here!</h1>
95
+ }
96
+ end
97
+ it_should_behave_like "the status is immutable"
98
+ it_should_behave_like "Content-Length is set"
99
+
100
+ it "should not add any highlight markup" do
101
+ response = pygmoku.call(env)[2].join
102
+ document = Nokogiri::HTML(response, nil, 'utf-8')
103
+ document.css('div.highlight').should be_empty
104
+ end
105
+ end
106
+ end
107
+
108
+ context "and the content is not HTML" do
109
+ let(:content_type) { 'application/xml' }
110
+
111
+ it_should_behave_like "pass-through middleware"
112
+ end
113
+ end
114
+
115
+ context "when the response is not 200" do
116
+ let(:status) { 301 }
117
+
118
+ it_should_behave_like "pass-through middleware"
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'rspec'
4
+ require 'rack/utils'
5
+ require 'rack/pygmoku'
6
+
7
+ # Requires supporting files with custom matchers and macros, etc,
8
+ # in ./support/ and its subdirectories.
9
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
10
+
11
+ RSpec.configure do |config|
12
+ config.mock_with :rspec
13
+ end
@@ -0,0 +1,16 @@
1
+ module Specs
2
+ module HtmlHelper
3
+ def html_doc_containing(body)
4
+ html =
5
+ %Q{<!DOCTYPE HTML>
6
+ <html>
7
+ <head><title>Test Markup</title></head>
8
+ <body>
9
+ #{body}
10
+ </body>
11
+ </html>
12
+ }
13
+ [html]
14
+ end
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,200 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rack-pygmoku
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Kevin Rohrbaugh
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-04-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: 1.5.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: pygments.rb
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: 0.2.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: 0.2.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: rack
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: bundler
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: 1.1.0
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: 1.1.0
78
+ - !ruby/object:Gem::Dependency
79
+ name: rake
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.9.0
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.9.0
94
+ - !ruby/object:Gem::Dependency
95
+ name: rdoc
96
+ requirement: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ~>
100
+ - !ruby/object:Gem::Version
101
+ version: '3.12'
102
+ type: :development
103
+ prerelease: false
104
+ version_requirements: !ruby/object:Gem::Requirement
105
+ none: false
106
+ requirements:
107
+ - - ~>
108
+ - !ruby/object:Gem::Version
109
+ version: '3.12'
110
+ - !ruby/object:Gem::Dependency
111
+ name: rspec
112
+ requirement: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ~>
116
+ - !ruby/object:Gem::Version
117
+ version: 2.9.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ~>
124
+ - !ruby/object:Gem::Version
125
+ version: 2.9.0
126
+ - !ruby/object:Gem::Dependency
127
+ name: watchr
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ~>
132
+ - !ruby/object:Gem::Version
133
+ version: '0.7'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ~>
140
+ - !ruby/object:Gem::Version
141
+ version: '0.7'
142
+ description: ! 'Rack middleware for Pygments use in environments you cannot install
143
+ Pygments
144
+
145
+ directly (e.g., Heroku).
146
+
147
+ '
148
+ email: kevin@rohrbaugh.us
149
+ executables: []
150
+ extensions: []
151
+ extra_rdoc_files:
152
+ - LICENSE.txt
153
+ - README.markdown
154
+ files:
155
+ - .document
156
+ - CHANGELOG
157
+ - Gemfile
158
+ - Gemfile.lock
159
+ - LICENSE.txt
160
+ - README.markdown
161
+ - Rakefile
162
+ - lib/rack/pygmoku.rb
163
+ - lib/rack/pygmoku/version.rb
164
+ - spec/lib/rack/pygmoku/version_spec.rb
165
+ - spec/lib/rack/pygmoku_spec.rb
166
+ - spec/spec_helper.rb
167
+ - spec/support/html_helper.rb
168
+ homepage: http://github.com/krohrbaugh/rack-pygmoku
169
+ licenses:
170
+ - MIT
171
+ post_install_message:
172
+ rdoc_options: []
173
+ require_paths:
174
+ - lib
175
+ required_ruby_version: !ruby/object:Gem::Requirement
176
+ none: false
177
+ requirements:
178
+ - - ! '>='
179
+ - !ruby/object:Gem::Version
180
+ version: '0'
181
+ segments:
182
+ - 0
183
+ hash: -4299723835127314902
184
+ required_rubygems_version: !ruby/object:Gem::Requirement
185
+ none: false
186
+ requirements:
187
+ - - ! '>='
188
+ - !ruby/object:Gem::Version
189
+ version: 1.5.0
190
+ requirements: []
191
+ rubyforge_project:
192
+ rubygems_version: 1.8.21
193
+ signing_key:
194
+ specification_version: 3
195
+ summary: Rack middleware for Pygments-based syntax highlighting
196
+ test_files:
197
+ - spec/lib/rack/pygmoku/version_spec.rb
198
+ - spec/lib/rack/pygmoku_spec.rb
199
+ - spec/spec_helper.rb
200
+ - spec/support/html_helper.rb