rack-esi 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -1,21 +1,4 @@
1
- ## MAC OS
2
- .DS_Store
3
-
4
- ## TEXTMATE
5
- *.tmproj
6
- tmtags
7
-
8
- ## EMACS
9
- *~
10
- \#*
11
- .\#*
12
-
13
- ## VIM
14
- *.swp
15
-
16
- ## PROJECT::GENERAL
17
- coverage
18
- rdoc
19
- pkg
20
-
21
- ## PROJECT::SPECIFIC
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source "http://rubygems.org"
2
+ gemspec
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009 Florian Assmann
1
+ Copyright (c) 2009 Florian Aßmann
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -0,0 +1,75 @@
1
+ # rack-esi
2
+
3
+ Rack-ESI is a Nokogiri based ESI middleware implementation for Rack with support for include tags, all other ESI namespaced nodes are just removed.
4
+
5
+ To make this gem work you must define the [xmlns:esi](http://www.edge-delivery.org/esi/1.0) namespace in your text/html response.
6
+
7
+ Note: This gem should only be used in development. For production use setup varnish or any other ESI enabled server.
8
+
9
+ ## Features
10
+
11
+ * threaded (in case we have slow IOs)
12
+ * PATH_INFO blacklisting (:skip => nil, should respond to ===)
13
+ * support for esi|include[alt] and esi|include[noerror] fallbacks
14
+
15
+ ## Dependencies
16
+
17
+ * Nokogiri
18
+ * Rack
19
+
20
+ ## Setup
21
+
22
+ ### w/o Gemfile
23
+
24
+ $ gem install rack-esi
25
+
26
+ ### w/ Gemfile
27
+
28
+ gem 'rack-esi'
29
+
30
+ ### rackup
31
+
32
+ use Rack::ESI, options || {}
33
+ run Application.new
34
+
35
+ ### Rails 2.3: environment.rb
36
+
37
+ config.gem 'rack-esi' # for setups w/o Gemfile
38
+ config.middleware.unshift Rack::ESI
39
+
40
+ ### Rails 3.1: application.rb
41
+
42
+ config.middleware.insert_before ActionDispatch::Static, Rack::ESI
43
+
44
+ ## Options
45
+
46
+ * poolsize: 4,
47
+ Number of worker threads. A value of 1 disables threading model.
48
+ * skip: nil,
49
+ This should be an object which responds to #===(PATH_INFO).
50
+ * parser: Nokogiri::XML::Document,
51
+ You can change this to Nokogiri::HTML::Document, but you should change the serializer, too (see below).
52
+ * serializer: :to_xhtml,
53
+ The serializer value specifies the method name which is send to the object created by the parser#parse.
54
+
55
+ ## TODO
56
+
57
+ * write documentation
58
+ * write more tests
59
+ * support more ESI elements
60
+
61
+ ## Note on Patches/Pull Requests
62
+
63
+ * Fork the project.
64
+ * Make your feature addition or bug fix.
65
+ * Add tests for it.
66
+ * Commit, do not mess with rakefile, version, or history.
67
+ * Send me a pull request.
68
+
69
+ ## Thanks
70
+
71
+ tenderlove and Qerub
72
+
73
+ ## Copyright
74
+
75
+ Copyright (c) 2009 Florian Aßmann. See LICENSE for details.
data/Rakefile CHANGED
@@ -1,53 +1 @@
1
- require 'rubygems'
2
- require 'rake'
3
-
4
- begin
5
- require 'jeweler'
6
- Jeweler::Tasks.new do |gem|
7
- gem.name = "rack-esi"
8
- gem.summary = %Q{ESI middleware implementation for Rack.}
9
- gem.description = %Q{Nokogiri based ESI middleware implementation for Rack with (limited) support for include, remove and comment.}
10
- gem.email = "florian.assmann@email.de"
11
- gem.homepage = "http://github.com/boof/rack-esi"
12
- gem.authors = ["Florian Aßmann"]
13
- gem.add_development_dependency "riot", ">= 0"
14
- gem.add_development_dependency "yard", ">= 0"
15
- gem.add_dependency 'nokogiri', '>= 0'
16
- end
17
- Jeweler::GemcutterTasks.new
18
- rescue LoadError
19
- puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
- end
21
-
22
- require 'rake/testtask'
23
- Rake::TestTask.new(:test) do |test|
24
- test.libs << 'lib' << 'test'
25
- test.pattern = 'test/**/*_test.rb'
26
- test.verbose = true
27
- end
28
-
29
- begin
30
- require 'rcov/rcovtask'
31
- Rcov::RcovTask.new do |test|
32
- test.libs << 'test'
33
- test.pattern = 'test/**/*_test.rb'
34
- test.verbose = true
35
- end
36
- rescue LoadError
37
- task :rcov do
38
- abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
- end
40
- end
41
-
42
- task :test => :check_dependencies
43
-
44
- task :default => :test
45
-
46
- begin
47
- require 'yard'
48
- YARD::Rake::YardocTask.new
49
- rescue LoadError
50
- task :yardoc do
51
- abort "YARD is not available. In order to run yardoc, you must: sudo gem install yard"
52
- end
53
- end
1
+ require "bundler/gem_tasks"
@@ -1,97 +1,45 @@
1
1
  require 'rack'
2
2
  require 'nokogiri'
3
+ require 'bundler'
4
+ Bundler.require
3
5
 
4
- class Rack::ESI
5
- NS = { 'esi' => 'http://www.edge-delivery.org/esi/1.0' }
6
- METHODS = { 'include' => :esi_include, 'remove' => nil, 'comment' => nil }
7
- CSS = METHODS.keys.map { |cmd| "esi|#{ cmd }" } * ','
6
+ require File.expand_path('../rack-esi/processor', __FILE__)
8
7
 
9
- class Error < RuntimeError
10
- def initialize(status, headers, response)
11
- @status, @headers, @response = status, headers, response
12
- end
13
- def finish
14
- return [@status, @headers, backtrace]
15
- end
16
- end
8
+ class Rack::ESI
17
9
 
18
10
  def initialize(app, options = {})
19
- @app = app
11
+ @app = app
20
12
 
21
- @paths = options[:skip]
22
- @types = options[:only] || /^text\/(?:x|ht)ml/
23
- @max_includes = options[:includes] || 32
24
- @max_recursion = options[:depth] || 5
13
+ @parser = options.fetch :parser, Nokogiri::XML::Document
14
+ @serializer = options.fetch :serializer, :to_xhtml
15
+ @skip = options[:skip]
16
+ @poolsize = options.fetch :poolsize, 4
17
+ @processor = @poolsize == 1 ? Processor::Linear : Processor::Threaded
25
18
  end
26
19
 
27
- def call env, counter = { :recursion => 0, :includes => 0 }
28
- return @app.call(env) if skip_path? env['PATH_INFO']
29
-
30
- status, headers, input = @app.call env.dup
31
- return status, headers, input if skip_type? headers['Content-Type']
32
-
33
- output = []
34
- input.each { |body| output << compile_body(body, env, counter) }
35
-
36
- Rack::Response.new(output, status, headers).finish
37
- end
38
-
39
- private
40
-
41
- def with_compiled_path(env, path)
42
- # TODO: should compile variables.
43
- env.merge 'PATH_INFO' => path, 'REQUEST_URI' => path
44
- end
20
+ def queue(&block)
21
+ unless @queue
22
+ @queue, @group = Queue.new, ThreadGroup.new
23
+ @poolsize.times { @group.add Worker.new(@queue) }
45
24
 
46
- def fetch(path, env, counter)
47
- call with_compiled_path(env, path), counter if path
48
- rescue => e
49
- return [500, {}, e.backtrace]
25
+ at_exit { Finisher.wait @queue }
50
26
  end
51
27
 
52
- # Should I use XML::SAX::Parser?
53
- def compile_body(body, env, counter)
54
- document = Nokogiri.XML body
28
+ @queue.push block
29
+ end
55
30
 
56
- document.css(CSS, NS).each do |node|
57
- method = METHODS[node.name] and send method, node, env, counter
58
- node.unlink
59
- end
31
+ attr_reader :parser, :serializer
60
32
 
61
- document.to_xhtml
62
- end
33
+ def call(env)
34
+ return @app.call(env) if @skip === env['PATH_INFO']
63
35
 
64
- def skip_path?(path)
65
- @paths =~ path if @paths
66
- end
67
- def skip_type?(type)
68
- @types !~ type
69
- end
36
+ status, headers, body = @app.call env.dup
70
37
 
71
- def max?(counter)
72
- not counter[:includes] < @max_includes &&
73
- counter[:recursion] < @max_recursion
38
+ if status == 200 and headers['Content-Type'] =~ /text\/html/
39
+ body = @processor.new(self, env).process body
74
40
  end
75
41
 
76
- def esi_include(node, env, counter)
77
- return if max? counter
78
-
79
- counter[:includes] += 1
80
- counter[:recursion] += 1
81
-
82
- status, headers, response = fetch node['src'], env, counter
83
- status, headers, response = fetch node['alt'], env, counter if status != 200
84
-
85
- if status == 200
86
- data = ''
87
- response.each { |inc| data << inc }
88
- node.before data
89
- elsif node['onerror'] != 'continue'
90
- raise Error.new(status, headers, response)
91
- end
92
-
93
- ensure
94
- counter[:recursion] -= 1
95
- end
42
+ return status, headers, body
43
+ end
96
44
 
97
45
  end
@@ -0,0 +1,55 @@
1
+ class Rack::ESI
2
+ class Processor < Struct.new(:esi, :env)
3
+
4
+ class Linear < self
5
+ def process_document(d)
6
+ d.xpath('//e:*', 'e' => NAMESPACE).each { |n| process_node n }
7
+ end
8
+ end
9
+ autoload :Threaded, File.expand_path('../threaded', __FILE__)
10
+
11
+ NAMESPACE = 'http://www.edge-delivery.org/esi/1.0'
12
+ Error = Class.new RuntimeError
13
+
14
+ def read(enumerable, buffer = '')
15
+ enumerable.each { |str| buffer << str }
16
+ buffer
17
+ end
18
+
19
+ def include(path)
20
+ # RADAR patron here?
21
+ esi.call env.merge('PATH_INFO' => path, 'REQUEST_URI' => path)
22
+ rescue => e
23
+ return 500, {}, []
24
+ end
25
+ def process_node(node)
26
+ case node.name
27
+ when 'include'
28
+ status, headers, body = include node['src']
29
+
30
+ unless status == 200 or node['alt'].nil?
31
+ status, headers, body = include node['alt']
32
+ end
33
+
34
+ if status == 200
35
+ node.replace read(body)
36
+ elsif node['onerror'] != 'continue'
37
+ raise Error
38
+ end
39
+ else
40
+ node.remove
41
+ end
42
+ end
43
+ def process_document(document)
44
+ raise NotImplementedError
45
+ end
46
+ def process(body)
47
+ document = esi.parser.parse read(body)
48
+ process_document document
49
+ [
50
+ document.send( esi.serializer )
51
+ ]
52
+ end
53
+
54
+ end
55
+ end
@@ -0,0 +1,50 @@
1
+ require 'thread'
2
+ #require 'timeout'
3
+
4
+ class Rack::ESI
5
+
6
+ class Finisher < Proc
7
+ def self.wait(queue)
8
+ finisher = new do |worker|
9
+ puts "Finishing #{ worker.inspect }..."
10
+ worker[:finish] = true
11
+ queue.push finisher
12
+ end
13
+
14
+ # cast the first stone
15
+ queue.push finisher
16
+
17
+ # wait at the end
18
+ queue.pop
19
+ end
20
+ end
21
+
22
+ class Worker < Thread
23
+ def initialize(queue)
24
+ super do
25
+ begin
26
+ queue.pop[ self ]
27
+ rescue => e
28
+ puts e
29
+ end until key? :finish
30
+ end
31
+ end
32
+ end
33
+
34
+ class Processor::Threaded < Processor
35
+ def process_document(document)
36
+ nodes = document.xpath '//e:*', 'e' => NAMESPACE
37
+
38
+ countdown, main = nodes.length, Thread.current
39
+ nodes.each do |node|
40
+ esi.queue do
41
+ process_node node
42
+ main.run if (countdown -= 1).zero?
43
+ end
44
+ end
45
+ # TODO prevent nesting depth bigger than poolsize
46
+ Thread.stop if countdown > 0 # wait for worker
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,5 @@
1
+ module Rack
2
+ class ESI
3
+ VERSION = "0.2.0"
4
+ end
5
+ end
@@ -0,0 +1,28 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "rack-esi/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "rack-esi"
7
+ s.version = Rack::ESI::VERSION
8
+ s.authors = ["Florian Aßmann"]
9
+ s.email = ["florian.assmann@email.de"]
10
+ s.homepage = ""
11
+ s.summary = %q{ ESI middleware implementation for Rack. }
12
+ s.description = <<-EOF
13
+ Rack-ESI is a Nokogiri based ESI middleware implementation for Rack with support for include tags, all other ESI namespaced nodes are just removed.
14
+ To make this gem work you must define the (xmlns:esi)[http://www.edge-delivery.org/esi/1.0] namespace in your text/html response.
15
+ Note: This gem should only be used in development. For production use setup varnish or any other ESI enabled server.
16
+ EOF
17
+ # s.rubyforge_project = "rack-esi"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+
24
+ s.add_dependency "rack"
25
+ s.add_dependency "nokogiri"
26
+ # s.add_dependency "patron"
27
+ s.add_development_dependency "riot"
28
+ end
@@ -0,0 +1,31 @@
1
+ require File.expand_path('../teststrap', __FILE__)
2
+
3
+ context 'Rack::ESI' do
4
+
5
+ __dirname__ = File.expand_path File.dirname(__FILE__)
6
+ root = Pathname.new File.join(__dirname__, 'fixtures')
7
+ opts = { :urls => ['/'], :root => root }
8
+
9
+ setup { ESI.new Static.new(App.new, opts), skip: /raw/, :poolsize => 1 }
10
+
11
+ context 'GET /raw.html' do
12
+ setup { MockRequest.new(topic).get '/raw.html' }
13
+ asserts('Content-Type') { topic.content_type }.equals 'text/html'
14
+ should('not be altered') { topic.body == root.join('raw.html').read }
15
+ end
16
+
17
+ context 'GET /index.html' do
18
+ setup { MockRequest.new(topic).get '/index.html' }
19
+
20
+ asserts('Content-Type') { topic.content_type }.equals 'text/html'
21
+ should('not have any ESI specific nodes') do
22
+ html(topic.body).
23
+ at('//e:*', 'e' => Rack::ESI::Processor::NAMESPACE).nil?
24
+ end
25
+ should('have meta replacement with content') do
26
+ not html(topic.body).
27
+ at("//meta[@name='replacement' and @content='content']").nil?
28
+ end
29
+ end
30
+
31
+ end
@@ -1,4 +1,4 @@
1
- require 'teststrap'
1
+ require File.expand_path('../teststrap', __FILE__)
2
2
 
3
3
  context 'Rack::ESI' do
4
4
 
@@ -6,7 +6,7 @@ context 'Rack::ESI' do
6
6
  root = Pathname.new File.join(__dirname__, 'fixtures')
7
7
  opts = { :urls => ['/'], :root => root }
8
8
 
9
- setup { ESI.new Static.new(App.new, opts), :skip => /raw/ }
9
+ setup { ESI.new Static.new(App.new, opts), skip: /raw/, :poolsize => 1 }
10
10
 
11
11
  context 'GET /raw.html' do
12
12
  setup { MockRequest.new(topic).get '/raw.html' }
@@ -20,7 +20,7 @@ context 'Rack::ESI' do
20
20
  asserts('Content-Type') { topic.content_type }.equals 'text/html'
21
21
  should('not have any ESI specific nodes') do
22
22
  html(topic.body).
23
- at('//esi:include|//esi:remove|//esi:comment', Rack::ESI::NS).nil?
23
+ at('//e:*', 'e' => Rack::ESI::Processor::NAMESPACE).nil?
24
24
  end
25
25
  should('have meta replacement with content') do
26
26
  not html(topic.body).
@@ -1,8 +1,12 @@
1
1
  require 'pathname'
2
- require 'rubygems'
3
- require 'riot'
4
- require 'rack-esi'
5
2
  require 'rack/mock'
3
+ require 'rack/static'
4
+ require 'rack/file'
5
+
6
+ require File.expand_path('../../lib/rack-esi', __FILE__)
7
+ Bundler.require :development
8
+
9
+ Nokogiri
6
10
 
7
11
  def html(body)
8
12
  Nokogiri.HTML(body).root
metadata CHANGED
@@ -1,97 +1,111 @@
1
- --- !ruby/object:Gem::Specification
1
+ --- !ruby/object:Gem::Specification
2
2
  name: rack-esi
3
- version: !ruby/object:Gem::Version
4
- version: 0.1.2
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ prerelease:
5
6
  platform: ruby
6
- authors:
7
- - "Florian A\xC3\x9Fmann"
7
+ authors:
8
+ - Florian Aßmann
8
9
  autorequire:
9
10
  bindir: bin
10
11
  cert_chain: []
11
-
12
- date: 2009-11-27 00:00:00 +01:00
13
- default_executable:
14
- dependencies:
15
- - !ruby/object:Gem::Dependency
16
- name: riot
17
- type: :development
18
- version_requirement:
19
- version_requirements: !ruby/object:Gem::Requirement
20
- requirements:
21
- - - ">="
22
- - !ruby/object:Gem::Version
23
- version: "0"
24
- version:
25
- - !ruby/object:Gem::Dependency
26
- name: yard
27
- type: :development
28
- version_requirement:
29
- version_requirements: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: "0"
34
- version:
35
- - !ruby/object:Gem::Dependency
12
+ date: 2011-09-30 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: &70252059616420 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70252059616420
25
+ - !ruby/object:Gem::Dependency
36
26
  name: nokogiri
27
+ requirement: &70252059615980 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
37
33
  type: :runtime
38
- version_requirement:
39
- version_requirements: !ruby/object:Gem::Requirement
40
- requirements:
41
- - - ">="
42
- - !ruby/object:Gem::Version
43
- version: "0"
44
- version:
45
- description: Nokogiri based ESI middleware implementation for Rack with (limited) support for include, remove and comment.
46
- email: florian.assmann@email.de
47
- executables: []
34
+ prerelease: false
35
+ version_requirements: *70252059615980
36
+ - !ruby/object:Gem::Dependency
37
+ name: riot
38
+ requirement: &70252059615560 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70252059615560
47
+ description: ! 'Rack-ESI is a Nokogiri based ESI middleware implementation for Rack
48
+ with support for include tags, all other ESI namespaced nodes are just removed.
48
49
 
49
- extensions: []
50
+ To make this gem work you must define the (xmlns:esi)[http://www.edge-delivery.org/esi/1.0]
51
+ namespace in your text/html response.
50
52
 
51
- extra_rdoc_files:
52
- - LICENSE
53
- - README.markdown
54
- files:
53
+ Note: This gem should only be used in development. For production use setup varnish
54
+ or any other ESI enabled server.
55
+
56
+ '
57
+ email:
58
+ - florian.assmann@email.de
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
55
63
  - .document
56
64
  - .gitignore
65
+ - Gemfile
66
+ - Gemfile.lock
57
67
  - LICENSE
58
- - README.markdown
68
+ - README.md
59
69
  - Rakefile
60
- - VERSION
61
70
  - lib/rack-esi.rb
71
+ - lib/rack-esi/processor.rb
72
+ - lib/rack-esi/threaded.rb
73
+ - lib/rack-esi/version.rb
74
+ - rack-esi.gemspec
75
+ - test/_test.rb
62
76
  - test/fixtures/index.html
63
77
  - test/fixtures/metatags.fragment
64
78
  - test/fixtures/raw.html
65
79
  - test/rack-esi_test.rb
66
80
  - test/teststrap.rb
67
- has_rdoc: true
68
- homepage: http://github.com/boof/rack-esi
81
+ homepage: ''
69
82
  licenses: []
70
-
71
83
  post_install_message:
72
- rdoc_options:
73
- - --charset=UTF-8
74
- require_paths:
84
+ rdoc_options: []
85
+ require_paths:
75
86
  - lib
76
- required_ruby_version: !ruby/object:Gem::Requirement
77
- requirements:
78
- - - ">="
79
- - !ruby/object:Gem::Version
80
- version: "0"
81
- version:
82
- required_rubygems_version: !ruby/object:Gem::Requirement
83
- requirements:
84
- - - ">="
85
- - !ruby/object:Gem::Version
86
- version: "0"
87
- version:
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ version: '0'
93
+ required_rubygems_version: !ruby/object:Gem::Requirement
94
+ none: false
95
+ requirements:
96
+ - - ! '>='
97
+ - !ruby/object:Gem::Version
98
+ version: '0'
88
99
  requirements: []
89
-
90
100
  rubyforge_project:
91
- rubygems_version: 1.3.5
101
+ rubygems_version: 1.8.8
92
102
  signing_key:
93
103
  specification_version: 3
94
104
  summary: ESI middleware implementation for Rack.
95
- test_files:
105
+ test_files:
106
+ - test/_test.rb
107
+ - test/fixtures/index.html
108
+ - test/fixtures/metatags.fragment
109
+ - test/fixtures/raw.html
96
110
  - test/rack-esi_test.rb
97
111
  - test/teststrap.rb
@@ -1,54 +0,0 @@
1
- # rack-esi
2
-
3
- Nokogiri based ESI middleware implementation for Rack with (limited) support
4
- for include, remove and comment.
5
-
6
- ## Features
7
-
8
- * path blacklisting (:skip => nil, expects Regexp)
9
- * type whitelisting (:only => /^text\/(?:x|ht)ml/)
10
- * recursion limit (:depth => 5)
11
- * include limits (:includes => 32)
12
- * support for &lt;include&gt; alt and noerror attributes
13
-
14
- _It's for development purpose..._
15
-
16
- ## Installation
17
-
18
- gem install rack-esi
19
-
20
- ## Rails Setup (environment.rb)
21
-
22
- config.gem 'rack-esi'
23
- require 'rack-esi'
24
- config.middleware.insert_before config.middleware.first, Rack::ESI
25
-
26
- ## TODO
27
-
28
- * write documentation
29
- * write more tests
30
- * support more ESI elements
31
- * switch to Nokogiri::XML::SAX::Document?
32
-
33
- ## Dependencies
34
-
35
- * Nokogiri
36
- * Rack
37
-
38
- ## Note on Patches/Pull Requests
39
-
40
- * Fork the project.
41
- * Make your feature addition or bug fix.
42
- * Add tests for it. This is important so I don't break it in a
43
- future version unintentionally.
44
- * Commit, do not mess with rakefile, version, or history.
45
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
46
- * Send me a pull request. Bonus points for topic branches.
47
-
48
- ## Thanks
49
-
50
- tenderlove and Qerub
51
-
52
- ## Copyright
53
-
54
- Copyright (c) 2009 Florian Assmann. See LICENSE for details.
data/VERSION DELETED
@@ -1 +0,0 @@
1
- 0.1.2