knot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/.rspec +1 -0
  2. data/.rvmrc +23 -0
  3. data/Gemfile +13 -0
  4. data/Gemfile.lock +37 -0
  5. data/README +2 -0
  6. data/Rakefile +15 -0
  7. data/config.ru +6 -0
  8. data/knot.gemspec +23 -0
  9. data/lib/knot/application/configuration.rb +25 -0
  10. data/lib/knot/application.rb +59 -0
  11. data/lib/knot/dispatch/filter.rb +27 -0
  12. data/lib/knot/dispatch/path.rb +38 -0
  13. data/lib/knot/dispatch/response.rb +10 -0
  14. data/lib/knot/dispatch/route.rb +22 -0
  15. data/lib/knot/dispatch/router/api.rb +39 -0
  16. data/lib/knot/dispatch/router/config.rb +53 -0
  17. data/lib/knot/dispatch/router/context.rb +39 -0
  18. data/lib/knot/dispatch/router.rb +53 -0
  19. data/lib/knot/dispatch.rb +5 -0
  20. data/lib/knot/render/binding.rb +16 -0
  21. data/lib/knot/render/template.rb +30 -0
  22. data/lib/knot/render.rb +16 -0
  23. data/lib/knot/util.rb +11 -0
  24. data/lib/knot.rb +10 -0
  25. data/spec/acceptance/acceptance_spec.rb +73 -0
  26. data/spec/acceptance/templates/override.html.erb +1 -0
  27. data/spec/acceptance/templates/test_router/override.html.erb +1 -0
  28. data/spec/acceptance/templates/test_template.html.erb +2 -0
  29. data/spec/acceptance/test_app.rb +50 -0
  30. data/spec/knot/dispatch/filter_spec.rb +31 -0
  31. data/spec/knot/dispatch/path_spec.rb +32 -0
  32. data/spec/knot/dispatch/response_spec.rb +12 -0
  33. data/spec/knot/dispatch/route_spec.rb +30 -0
  34. data/spec/knot/dispatch/router/config_spec.rb +12 -0
  35. data/spec/knot/dispatch/router_spec.rb +111 -0
  36. data/spec/knot/render/binding_spec.rb +13 -0
  37. data/spec/knot/render_spec.rb +34 -0
  38. data/spec/knot/util_spec.rb +9 -0
  39. data/unicorn.conf.rb +5 -0
  40. metadata +146 -0
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+
3
+ # This is an RVM Project .rvmrc file, used to automatically load the ruby
4
+ # development environment upon cd'ing into the directory
5
+
6
+ environment_id="ruby-1.9.3-p125@knot"
7
+
8
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments"
9
+ && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]
10
+ then
11
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
12
+ [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]] &&
13
+ \. "${rvm_path:-$HOME/.rvm}/hooks/after_use" || true
14
+ if [[ $- == *i* ]] # check for interactive shells
15
+ then echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)" # show the user the ruby and gemset they are using in green
16
+ else echo "Using: $GEM_HOME" # don't use colors in non-interactive shells
17
+ fi
18
+ else
19
+ rvm --create use "$environment_id" || {
20
+ echo "Failed to create RVM environment '${environment_id}'."
21
+ return 1
22
+ }
23
+ fi
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem "rack"
4
+ gem "rack-protection"
5
+ gem "yajl-ruby"
6
+
7
+
8
+ group :test, :development do
9
+ gem "rack-test"
10
+ gem "rspec"
11
+ gem "rake"
12
+ gem "unicorn"
13
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,37 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ diff-lcs (1.1.3)
5
+ kgio (2.7.4)
6
+ rack (1.4.1)
7
+ rack-protection (1.2.0)
8
+ rack
9
+ rack-test (0.6.1)
10
+ rack (>= 1.0)
11
+ raindrops (0.10.0)
12
+ rake (0.9.2.2)
13
+ rspec (2.8.0)
14
+ rspec-core (~> 2.8.0)
15
+ rspec-expectations (~> 2.8.0)
16
+ rspec-mocks (~> 2.8.0)
17
+ rspec-core (2.8.0)
18
+ rspec-expectations (2.8.0)
19
+ diff-lcs (~> 1.1.2)
20
+ rspec-mocks (2.8.0)
21
+ unicorn (4.3.1)
22
+ kgio (~> 2.6)
23
+ rack
24
+ raindrops (~> 0.7)
25
+ yajl-ruby (1.1.0)
26
+
27
+ PLATFORMS
28
+ ruby
29
+
30
+ DEPENDENCIES
31
+ rack
32
+ rack-protection
33
+ rack-test
34
+ rake
35
+ rspec
36
+ unicorn
37
+ yajl-ruby
data/README ADDED
@@ -0,0 +1,2 @@
1
+ # Knot
2
+ It's pretty much top secret.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ task :default => ["knot:test"]
2
+
3
+ namespace :knot do
4
+
5
+ desc "Start the server with unicorn"
6
+ task :start do
7
+ Kernel.system("unicorn -c unicorn.conf.rb")
8
+ end
9
+
10
+ desc "Run Tests"
11
+ task :test do
12
+ Kernel.system("bundle exec rspec")
13
+ end
14
+
15
+ end
data/config.ru ADDED
@@ -0,0 +1,6 @@
1
+ $:<< File.dirname(__FILE__)
2
+ $:<< File.join(File.dirname(__FILE__), "lib")
3
+
4
+ require 'spec/acceptance/test_app'
5
+
6
+ run TestApp.new
data/knot.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'knot/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "knot"
8
+ gem.version = Knot::VERSION
9
+ gem.authors = ["Brian Pratt"]
10
+ gem.email = ["brian@8thlight.com"]
11
+ gem.description = "Knot"
12
+ gem.summary = "Build ruby apps"
13
+ gem.homepage = ""
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_dependency('rack')
21
+ gem.add_dependency('rack-protection')
22
+ gem.add_dependency('yajl-ruby')
23
+ end
@@ -0,0 +1,25 @@
1
+ module Knot
2
+ class Application
3
+ class Configuration
4
+ attr_writer :template_path
5
+
6
+ def add_router(router)
7
+ routers << router
8
+ end
9
+
10
+ def routers
11
+ @routers ||= []
12
+ end
13
+
14
+ def template_path
15
+ @template_path ||= "templates/"
16
+ end
17
+
18
+ def finalize!
19
+ routers.each do |router|
20
+ router.config.knot_template_home = template_path
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,59 @@
1
+ require 'knot/dispatch/router'
2
+ require 'knot/application/configuration'
3
+ require 'rack/protection'
4
+
5
+ module Knot
6
+ class Application
7
+ class << self;
8
+ alias :new_without_middleware :new
9
+
10
+ def new
11
+ app = new_without_middleware
12
+ return build_rack(app).to_app
13
+ end
14
+
15
+ def build_rack(app)
16
+ builder = Rack::Builder.new
17
+ builder.use Rack::Session::Cookie
18
+ builder.use Rack::Protection
19
+ builder.run app
20
+ return builder
21
+ end
22
+
23
+ def configure(&block)
24
+ block.call(config)
25
+ config.finalize!
26
+ end
27
+
28
+ def config
29
+ @config ||= Knot::Application::Configuration.new
30
+ end
31
+ end
32
+
33
+ def call(env)
34
+ begin
35
+ route_request(env)
36
+ rescue => e
37
+ return [500, {'Content-Type' => 'text/html'}, ["An error occured: ", e.message + "\n", e.backtrace.join("\n")]]
38
+ end
39
+ end
40
+
41
+ def config
42
+ self.class.config
43
+ end
44
+
45
+ def route_request(env)
46
+ config.routers.each do |router|
47
+ begin
48
+ request = Rack::Request.new(env)
49
+ response = router._call!(request)
50
+ return response.to_rack
51
+ rescue Knot::Dispatch::NoRouteError
52
+ next
53
+ end
54
+ end
55
+
56
+ return [404, {"Content-Type" => "text/html"}, ["Aw snap"]]
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,27 @@
1
+ require 'knot/dispatch'
2
+
3
+ module Knot::Dispatch
4
+ class Filter
5
+ attr_reader :block
6
+
7
+ def initialize(name, options = {}, &block)
8
+ @name = name
9
+ @options = {:only => [], :except => []}.merge(options)
10
+ @block = block
11
+ end
12
+
13
+ def apply_to?(route)
14
+ (only.empty? && except.empty?) ||
15
+ (!only.empty? && only.include?(route.name)) ||
16
+ (!except.empty? && !except.include?(route.name))
17
+ end
18
+
19
+ def only
20
+ @options[:only]
21
+ end
22
+
23
+ def except
24
+ @options[:except]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,38 @@
1
+ require 'knot/dispatch'
2
+
3
+ module Knot::Dispatch
4
+ class Path
5
+ def self.match?(subject, other)
6
+ return false if chunks(subject).length != chunks(other).length
7
+ pairs(subject, other).all? do |subject_chunk, other_chunk|
8
+ variable?(subject_chunk) || subject_chunk == other_chunk
9
+ end
10
+ end
11
+
12
+ def self.path_params(subject, request)
13
+ url_params = pairs(subject, request.path).inject({}) do |hash, values|
14
+ key, value = values
15
+ hash[symbolize(key)] = value if variable?(key)
16
+ hash
17
+ end
18
+ end
19
+
20
+ private
21
+
22
+ def self.pairs(subject, other)
23
+ chunks(subject).zip(chunks(other))
24
+ end
25
+
26
+ def self.symbolize(value)
27
+ value.sub(":", "").to_sym
28
+ end
29
+
30
+ def self.variable?(chunk)
31
+ chunk[0, 1] == ":"
32
+ end
33
+
34
+ def self.chunks(path)
35
+ path.split("/")
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,10 @@
1
+ require 'knot/dispatch'
2
+ require 'rack/response'
3
+
4
+ module Knot::Dispatch
5
+ class Response < ::Rack::Response
6
+ def to_rack
7
+ [status, headers, body]
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ require 'knot/dispatch'
2
+ require 'knot/dispatch/path'
3
+
4
+ module Knot::Dispatch
5
+ class Route
6
+ attr_reader :path, :http_method, :name, :block
7
+
8
+ def initialize(http_method, name, path, &block)
9
+ @http_method, @name, @path, @block = http_method.to_s.upcase, name, path, block
10
+ end
11
+
12
+ def match?(request)
13
+ http_method_match?(request) && Path.match?(@path, request.path)
14
+ end
15
+
16
+ private
17
+
18
+ def http_method_match?(request)
19
+ request.request_method == @http_method
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,39 @@
1
+ require 'knot/dispatch'
2
+
3
+ module Knot::Dispatch
4
+ class Router
5
+ module API
6
+ def get(name, path, &block)
7
+ config.register_route(:get, name, path, &block)
8
+ end
9
+
10
+ def post(name, path, &block)
11
+ config.register_route(:post, name, path, &block)
12
+ end
13
+
14
+ def put(name, path, &block)
15
+ config.register_route(:put, name, path, &block)
16
+ end
17
+
18
+ def delete(name, path, &block)
19
+ config.register_route(:delete, name, path, &block)
20
+ end
21
+
22
+ def patch(name, path, &block)
23
+ config.register_route(:patch, name, path, &block)
24
+ end
25
+
26
+ def head(name, path, &block)
27
+ config.register_route(:head, name, path, &block)
28
+ end
29
+
30
+ def options(name, path, &block)
31
+ config.register_route(:options, name, path, &block)
32
+ end
33
+
34
+ def filter(name, options, &block)
35
+ config.add_filter(name, options, &block)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,53 @@
1
+ require 'knot/dispatch/route'
2
+ require 'knot/dispatch/filter'
3
+ require 'knot/util'
4
+
5
+ module Knot
6
+ module Dispatch
7
+ class Router
8
+ class Config
9
+ attr_reader :router_name
10
+
11
+ def initialize(router)
12
+ @router_name = router.to_s
13
+ end
14
+
15
+ def routes
16
+ @routes ||= []
17
+ end
18
+
19
+ def register_route(http_method, name, path, &block)
20
+ routes << Knot::Dispatch::Route.new(http_method, name, path, &block)
21
+ end
22
+
23
+ def filters
24
+ @filters ||= []
25
+ end
26
+
27
+ def add_filter(name, options, &block)
28
+ filters << Knot::Dispatch::Filter.new(name, options, &block)
29
+ end
30
+
31
+ def knot_template_home=(base_path)
32
+ @template_paths = template_paths.
33
+ reject { |path| path == base_path }.
34
+ map { |path| path.prepend(base_path + "/") }
35
+ template_paths << base_path
36
+ end
37
+
38
+ def template_paths
39
+ @template_paths ||= [Knot::Util.to_snake(router_name)]
40
+ end
41
+
42
+ def applicable_filters(route)
43
+ filters.select {|f| f.apply_to?(route)}
44
+ end
45
+
46
+ def reset!
47
+ @routes = []
48
+ @filters = []
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,39 @@
1
+ require 'knot/render'
2
+ require 'knot/dispatch'
3
+ require 'yajl/json_gem'
4
+
5
+ module Knot::Dispatch
6
+ class Router
7
+ module Context
8
+ def params
9
+ request.params
10
+ end
11
+
12
+ def status(code)
13
+ response.status = code
14
+ end
15
+
16
+ def redirect(uri)
17
+ status(302)
18
+ response.headers['Location'] = uri
19
+ respond!
20
+ end
21
+
22
+ def json(obj)
23
+ response.body << obj.to_json
24
+ response.headers["Content-Type"] = "application/json"
25
+ respond!
26
+ end
27
+
28
+ def render(filename, options = {})
29
+ response.body << Knot::Render.render(filename, _config.template_paths, options)
30
+ respond!
31
+ end
32
+
33
+ # terminate processing and return to router to handle the given response
34
+ def respond!
35
+ throw(:halt, response)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,53 @@
1
+ require 'knot/dispatch'
2
+ require 'knot/dispatch/router/config'
3
+ require 'knot/dispatch/router/context'
4
+ require 'knot/dispatch/router/api'
5
+ require 'knot/dispatch/response'
6
+
7
+ module Knot::Dispatch
8
+ class Router
9
+ include Knot::Dispatch::Router::Context
10
+ extend Knot::Dispatch::Router::API
11
+
12
+ def self._call!(request)
13
+ new(request)._call!
14
+ end
15
+
16
+ def self.config
17
+ @config ||= Knot::Dispatch::Router::Config.new(self)
18
+ end
19
+
20
+ attr_reader :request, :response
21
+
22
+ def initialize(request)
23
+ @request = request
24
+ @response = Knot::Dispatch::Response.new
25
+ end
26
+
27
+ def _call!
28
+ route = _config.routes.find { |route| route.match?(request) }
29
+
30
+ raise Knot::Dispatch::NoRouteError, "no route matches #{request.path}" if route.nil?
31
+
32
+ request.params.merge! Path.path_params(route.path, request)
33
+
34
+ blocks = _config.applicable_filters(route).map(&:block)
35
+ blocks << route.block
36
+
37
+ catch(:halt) {
38
+ blocks.map do |block|
39
+ result = instance_eval &block
40
+ response.body << result if result.is_a?(String)
41
+ end.last
42
+ }
43
+
44
+ return response
45
+ end
46
+
47
+ private
48
+
49
+ def _config
50
+ self.class.config
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,5 @@
1
+ module Knot
2
+ module Dispatch
3
+ NoRouteError = Class.new(RuntimeError)
4
+ end
5
+ end
@@ -0,0 +1,16 @@
1
+ module Knot
2
+ module Render
3
+ class Binding
4
+ def initialize(options)
5
+ options.each do |key, value|
6
+ instance_variable_set("@#{key.to_s}", value)
7
+ self.class.instance_eval { attr_reader key }
8
+ end
9
+ end
10
+
11
+ def get_binding
12
+ binding
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,30 @@
1
+ module Knot
2
+ module Render
3
+ class Template
4
+ def self.for(filename, search_paths)
5
+ filename += ".html.erb"
6
+ directory = search_paths.find { |path| File.exist?(File.join(path, filename)) }
7
+
8
+ raise NoTemplateError, "couldn't find template #{filename} in #{search_paths.join(", ")}" if directory.nil?
9
+
10
+ template_for(File.join(directory, filename))
11
+ end
12
+
13
+ def self.template_for(filename)
14
+ if cache.keys.include?(filename)
15
+ cache[filename]
16
+ else
17
+ cache[filename] = ERB.new(File.read(filename))
18
+ end
19
+ end
20
+
21
+ def self.cache
22
+ @cache ||= {}
23
+ end
24
+
25
+ def self.clear_cache!
26
+ @cache = {}
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,16 @@
1
+ require 'knot/render/binding'
2
+ require 'knot/render/template'
3
+
4
+ module Knot
5
+ module Render
6
+
7
+ NoTemplateError = Class.new(RuntimeError)
8
+
9
+ def self.render(filename, search_paths, options = {})
10
+ binding = Knot::Render::Binding.new(options)
11
+ template = Knot::Render::Template.for(filename, search_paths)
12
+
13
+ return template.result(binding.get_binding)
14
+ end
15
+ end
16
+ end
data/lib/knot/util.rb ADDED
@@ -0,0 +1,11 @@
1
+ module Knot
2
+ class Util
3
+ def self.to_snake(str)
4
+ str.to_s.gsub(/::/, '/').
5
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
6
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
7
+ tr("-", "_").
8
+ downcase
9
+ end
10
+ end
11
+ end
data/lib/knot.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'knot/dispatch/router'
2
+ require 'knot/application'
3
+
4
+ module Knot
5
+ Router = Dispatch::Router
6
+
7
+ def self.env
8
+ ENV['RACK_ENV']
9
+ end
10
+ end
@@ -0,0 +1,73 @@
1
+ require 'rack/test'
2
+ require './spec/acceptance/test_app'
3
+
4
+ describe "basic test for API" do
5
+ let(:app) { TestApp.new }
6
+ let!(:request) { Rack::MockRequest.new(app) }
7
+
8
+ it "routes to root" do
9
+ response = request.get "/"
10
+ response.body.should == "Huzzah"
11
+ end
12
+
13
+ it "gets URL params" do
14
+ response = request.get "/greet/world"
15
+ response.body.should == "Hello, world"
16
+
17
+ other_response = request.get "/greet/nurse"
18
+ other_response.body.should == "Hello, nurse"
19
+ end
20
+
21
+ it "returns 404" do
22
+ response = request.get "/not_to_be_found"
23
+ response.status.should == 404
24
+ response.body.should == "Aw snap"
25
+ end
26
+
27
+ it "redirects" do
28
+ response = request.get "/onward"
29
+ response.should be_redirect
30
+ response.headers['Location'].should == "/greet/redirect"
31
+ end
32
+
33
+ context "json" do
34
+ it "renders json" do
35
+ response = request.get "/as_json"
36
+ response.status.should == 200
37
+ response.headers["Content-Type"].should == "application/json"
38
+ response.body.should == "{\"key\":\"value\"}"
39
+ end
40
+ end
41
+
42
+ context "when an exception is raised" do
43
+ it "returns 500" do
44
+ response = request.get "/raise_exception"
45
+ response.status.should == 500
46
+ end
47
+ end
48
+
49
+ context "filters" do
50
+ it "when filter is satisfied" do
51
+ response = request.get "/filter/pass"
52
+ response.body.should == "Filtered"
53
+ end
54
+
55
+ it "when filter is not satisfied" do
56
+ response = request.get "/filter/fail"
57
+ response.should be_redirect
58
+ end
59
+ end
60
+
61
+ context "templates" do
62
+ it "renders the template in the app's template path" do
63
+ response = request.get "/template/someone"
64
+ response.body.should == "<h1>Hello, someone,</h1>\n<p>from ERB!</p>\n"
65
+ end
66
+
67
+ it "renders the template in the router's template path if it exists" do
68
+ response = request.get "/template_override"
69
+ response.body.should == "Should render me.\n"
70
+ end
71
+ end
72
+ end
73
+
@@ -0,0 +1 @@
1
+ Shouldn't render me.
@@ -0,0 +1 @@
1
+ Should render me.
@@ -0,0 +1,2 @@
1
+ <h1>Hello, <%= name %>,</h1>
2
+ <p>from ERB!</p>
@@ -0,0 +1,50 @@
1
+ require "knot"
2
+
3
+ class TestRouter < Knot::Router
4
+
5
+ filter :redirect_unless_pass, :only => [:filter_test] do
6
+ redirect "/greet/nurse" unless params[:filter] == "pass"
7
+ end
8
+
9
+ def yay
10
+ "Huzzah"
11
+ end
12
+
13
+ get(:index, "/") { yay }
14
+
15
+ get :url_param_test, "/greet/:slug" do
16
+ "Hello, " + params[:slug]
17
+ end
18
+
19
+ get :redirect_test, "/onward" do
20
+ redirect "/greet/redirect"
21
+ end
22
+
23
+ get :json_test, "/as_json" do
24
+ json key: "value"
25
+ end
26
+
27
+ get :exception_test, "/raise_exception" do
28
+ raise "whoa nelly!!"
29
+ end
30
+
31
+ get :filter_test, "/filter/:filter" do
32
+ "Filtered"
33
+ end
34
+
35
+ get :template_test, "/template/:name" do
36
+ render 'test_template', :name => params[:name]
37
+ end
38
+
39
+ get :template_override_test, "/template_override" do
40
+ render 'override'
41
+ end
42
+ end
43
+
44
+ class TestApp < Knot::Application
45
+ configure do |config|
46
+ config.add_router TestRouter
47
+ config.template_path = "spec/acceptance/templates"
48
+ end
49
+ end
50
+
@@ -0,0 +1,31 @@
1
+ require 'knot/dispatch/filter'
2
+
3
+ describe Knot::Dispatch::Filter do
4
+ context "apply_to?" do
5
+ context "apply to all" do
6
+ it "applies to any route" do
7
+ filter = Knot::Dispatch::Filter.new(:example) { "called" }
8
+ filter.apply_to?(stub(:name => :route)).should be_true
9
+ end
10
+ end
11
+
12
+ context "only" do
13
+ it "applies if the route is in the only list" do
14
+ filter = Knot::Dispatch::Filter.new(:example, :only => [:route]) { "called" }
15
+ filter.apply_to?(stub(:name => :route)).should be_true
16
+ end
17
+
18
+ it "does not apply if the route is not in the only list" do
19
+ filter = Knot::Dispatch::Filter.new(:example, :only => [:route]) { "called" }
20
+ filter.apply_to?(stub(:name => :another)).should_not be_true
21
+ end
22
+ end
23
+
24
+ context "except" do
25
+ it "applies if the route is not in the except list" do
26
+ filter = Knot::Dispatch::Filter.new(:example, :except => [:route]) { "called" }
27
+ filter.apply_to?(stub(:name => :another)).should be_true
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,32 @@
1
+ require 'knot/dispatch/path'
2
+
3
+ describe Knot::Dispatch::Path do
4
+ context ".match?" do
5
+ subject { Knot::Dispatch::Path }
6
+
7
+ it "fails for wrong route" do
8
+ subject.match?("/test", "/asdf").should_not be_true
9
+ end
10
+
11
+ it "matches a simple path" do
12
+ subject.match?("/test", "/test").should be_true
13
+ end
14
+
15
+ it "matches a path with param injection" do
16
+ subject.match?("/test/:id", "/test/1").should be_true
17
+ end
18
+
19
+ it "matches a nested path with multiple params injected" do
20
+ subject.match?("/test/:foo/and/:bar/edit","/test/1/and/2/edit").should be_true
21
+ end
22
+ end
23
+
24
+ context ".path_params" do
25
+ it "gets the variables from path and inserts them into a hash" do
26
+ request = stub(:path => "/test/1/and/2/edit")
27
+
28
+ vars = Knot::Dispatch::Path.path_params("/test/:foo/and/:bar/edit", request)
29
+ vars.should == {:foo => "1", :bar => "2"}
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,12 @@
1
+ require 'knot/dispatch/response'
2
+
3
+ describe Knot::Dispatch::Response do
4
+ context "#to_rack" do
5
+ it "returns the array for rack interface" do
6
+ response = Knot::Dispatch::Response.new
7
+ response.body << 'Test'
8
+ response.status = 200
9
+ response.to_rack.should == [200, {"Content-Type"=>"text/html"}, ['Test']]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ require 'knot/dispatch/route'
2
+
3
+ describe Knot::Dispatch::Route do
4
+ it "target path" do
5
+ route = Knot::Dispatch::Route.new("GET", :test, "/test")
6
+ route.path.should == "/test"
7
+ end
8
+
9
+ it "http method" do
10
+ route = Knot::Dispatch::Route.new("GET", :test, "/test")
11
+ route.http_method.should == "GET"
12
+ end
13
+
14
+ context "#match?" do
15
+ let(:route) { Knot::Dispatch::Route.new("GET", :test, "/test") }
16
+
17
+ it "delegates to Path" do
18
+ request = stub(:request_method => "GET", :path => "/test", :params => {})
19
+ Knot::Dispatch::Path.should_receive(:match?).with(route.path, request.path).and_return(true)
20
+ route.match?(request).should be_true
21
+ end
22
+
23
+ it "filters paths out by http_method" do
24
+ request = stub(:request_method => "POST", :path => "/test", :params => {})
25
+ Knot::Dispatch::Path.should_receive(:match?).never
26
+ route.match?(request).should_not be_true
27
+ end
28
+ end
29
+ end
30
+
@@ -0,0 +1,12 @@
1
+ require 'knot/dispatch/router/config'
2
+
3
+ describe Knot::Dispatch::Router::Config do
4
+ context "template_paths" do
5
+ it "defaults to an array with router's directory name in it" do
6
+ AnExampleRouter = Class.new unless defined?(AnExampleRouter)
7
+
8
+ config = Knot::Dispatch::Router::Config.new(AnExampleRouter)
9
+ config.template_paths.should == ["an_example_router"]
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,111 @@
1
+ require 'knot/dispatch/router'
2
+
3
+ describe Knot::Dispatch::Router do
4
+ before(:each) { router.config.reset! }
5
+ let(:router) { Knot::Dispatch::Router }
6
+
7
+ context "definition/configuration" do
8
+ context ".routes" do
9
+ it "has collection of routes" do
10
+ router.config.routes.should == []
11
+ end
12
+
13
+ context "registration" do
14
+ [:get, :post, :put, :delete, :patch, :head, :options].each do |http_method|
15
+ it "registers a #{http_method} route" do
16
+ expect {
17
+ router.send(http_method, :hey, "/") { "hey" }
18
+ }.to change { router.config.routes.size }.by(1)
19
+
20
+ router.config.routes.first.http_method.should == http_method.to_s.upcase
21
+ end
22
+ end
23
+ end
24
+ end
25
+
26
+ context ".call!" do
27
+ before(:each) do
28
+ router.get(:index, "/")
29
+ router.post(:test_action, "/test_action") do
30
+ "test_sentinel"
31
+ end
32
+ end
33
+
34
+ it "finds the route if it exists" do
35
+ request = stub(:request_method => "POST", :params => {}, :path => "/test_action")
36
+ router._call!(request).should_not raise_error Knot::Dispatch::NoRouteError
37
+ end
38
+
39
+ it "returns a response" do
40
+ request = stub(:request_method => "POST", :params => {}, :path => "/test_action")
41
+ router._call!(request).should be_a(Knot::Dispatch::Response)
42
+ end
43
+
44
+ it "raises NoRouteError if the route isn't found" do
45
+ request = stub(:request_method => "POST", :params => {}, :path => "/nonsense_path")
46
+
47
+ expect {
48
+ router._call!(request)
49
+ }.to raise_error Knot::Dispatch::NoRouteError
50
+ end
51
+
52
+ it "triggers the action for the route" do
53
+ request = stub(:request_method => "POST", :params => {}, :path => "/test_action")
54
+ router._call!(request).body.should == ["test_sentinel"]
55
+ end
56
+ end
57
+
58
+ context ".filter" do
59
+ it "defines a filter" do
60
+ request = stub(:request_method => "GET", :params => {}, :path => "/path")
61
+
62
+ router.filter :example_filter, :only => [:example] do
63
+ response.body << "Filtered!"
64
+ respond! # kill processing and respond immediately
65
+ end
66
+
67
+ router.get(:example, "/path") do |*_|
68
+ "Shouldn't get here"
69
+ end
70
+
71
+ router._call!(request).body.should == ["Filtered!"]
72
+ end
73
+ end
74
+ end
75
+
76
+ context "execution" do
77
+ let(:request) { stub(:request_method => "POST", :params => {:example_param => true}, :path => "/") }
78
+
79
+ context "#params" do
80
+ it "gives access to the request's params" do
81
+ router = Knot::Dispatch::Router.new(request)
82
+ router.params.should == request.params
83
+ end
84
+ end
85
+
86
+ context "#redirect" do
87
+ it "sets status and location header" do
88
+ router = Knot::Dispatch::Router.new(request)
89
+ response = catch(:halt) { router.redirect('http://www.google.com') }
90
+ response.status.should == 302
91
+ response.headers['Location'].should == "http://www.google.com"
92
+ end
93
+ end
94
+
95
+ context "#json" do
96
+ it "returns the object in json" do
97
+ router = Knot::Dispatch::Router.new(request)
98
+ response = catch(:halt) { router.json(:foo => "bar") }
99
+ JSON.parse(response.body.first).should == {'foo' => 'bar'}
100
+ end
101
+ end
102
+
103
+ context "#status" do
104
+ it "sets the status code on the response" do
105
+ router = Knot::Dispatch::Router.new(request)
106
+ router.status(422)
107
+ router.response.status.should == 422
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,13 @@
1
+ require 'knot/render/binding'
2
+
3
+ describe Knot::Render::Binding do
4
+ it "sets attributes as instance variables" do
5
+ binding = Knot::Render::Binding.new(:foo => "bar")
6
+ binding.foo.should == "bar"
7
+ end
8
+
9
+ it "gets the binding for the template to run against" do
10
+ binding = Knot::Render::Binding.new(:foo => "bar")
11
+ binding.get_binding.should be_a ::Binding
12
+ end
13
+ end
@@ -0,0 +1,34 @@
1
+ require 'knot/render'
2
+
3
+ describe Knot::Render do
4
+ context ".render" do
5
+ it "renders the template if found in given search path" do
6
+ filename = "dir/filename.html.erb"
7
+ File.stub(:exist?).with(filename).and_return(true)
8
+ File.stub(:read).with(filename).and_return("template")
9
+
10
+ Knot::Render.render("filename", ["dir"]).should == "template"
11
+ end
12
+
13
+ it "raises NoTemplateError if it can't find the template" do
14
+ filename = "dir/filename.html.erb"
15
+ File.stub(:exist?).with(filename).and_return(false)
16
+
17
+ expect {
18
+ Knot::Render.render("filename", ["dir"])
19
+ }.to raise_error Knot::Render::NoTemplateError
20
+ end
21
+
22
+ it "caches the results of the render" do
23
+ Knot::Render::Template.clear_cache!
24
+ filename = "dir/filename.html.erb"
25
+
26
+ File.stub(:exist?).with(filename).and_return(true)
27
+ File.should_receive(:read).once.with(filename).and_return("template")
28
+
29
+ Knot::Render.render("filename", ["dir"])
30
+ cached_result = Knot::Render.render("filename", ["dir"])
31
+ cached_result.should == "template"
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,9 @@
1
+ require 'knot/util'
2
+
3
+ describe Knot::Util do
4
+ context ".to_snake" do
5
+ it "converts a constant or camelcase to snake case" do
6
+ Knot::Util.to_snake("HelloWorld").should == "hello_world"
7
+ end
8
+ end
9
+ end
data/unicorn.conf.rb ADDED
@@ -0,0 +1,5 @@
1
+ listen 4567 # by default Unicorn listens on port 8080
2
+ worker_processes 1 # this should be >= nr_cpus
3
+ # pid "/path/to/app/shared/pids/unicorn.pid"
4
+ # stderr_path "/path/to/app/shared/log/unicorn.log"
5
+ # stdout_path "/path/to/app/shared/log/unicorn.log"
metadata ADDED
@@ -0,0 +1,146 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: knot
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Brian Pratt
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-22 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rack
16
+ requirement: !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: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: rack-protection
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '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'
46
+ - !ruby/object:Gem::Dependency
47
+ name: yajl-ruby
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
+ description: Knot
63
+ email:
64
+ - brian@8thlight.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .rspec
70
+ - .rvmrc
71
+ - Gemfile
72
+ - Gemfile.lock
73
+ - README
74
+ - Rakefile
75
+ - config.ru
76
+ - knot.gemspec
77
+ - lib/knot.rb
78
+ - lib/knot/application.rb
79
+ - lib/knot/application/configuration.rb
80
+ - lib/knot/dispatch.rb
81
+ - lib/knot/dispatch/filter.rb
82
+ - lib/knot/dispatch/path.rb
83
+ - lib/knot/dispatch/response.rb
84
+ - lib/knot/dispatch/route.rb
85
+ - lib/knot/dispatch/router.rb
86
+ - lib/knot/dispatch/router/api.rb
87
+ - lib/knot/dispatch/router/config.rb
88
+ - lib/knot/dispatch/router/context.rb
89
+ - lib/knot/render.rb
90
+ - lib/knot/render/binding.rb
91
+ - lib/knot/render/template.rb
92
+ - lib/knot/util.rb
93
+ - spec/acceptance/acceptance_spec.rb
94
+ - spec/acceptance/templates/override.html.erb
95
+ - spec/acceptance/templates/test_router/override.html.erb
96
+ - spec/acceptance/templates/test_template.html.erb
97
+ - spec/acceptance/test_app.rb
98
+ - spec/knot/dispatch/filter_spec.rb
99
+ - spec/knot/dispatch/path_spec.rb
100
+ - spec/knot/dispatch/response_spec.rb
101
+ - spec/knot/dispatch/route_spec.rb
102
+ - spec/knot/dispatch/router/config_spec.rb
103
+ - spec/knot/dispatch/router_spec.rb
104
+ - spec/knot/render/binding_spec.rb
105
+ - spec/knot/render_spec.rb
106
+ - spec/knot/util_spec.rb
107
+ - unicorn.conf.rb
108
+ homepage: ''
109
+ licenses: []
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ none: false
116
+ requirements:
117
+ - - ! '>='
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ none: false
122
+ requirements:
123
+ - - ! '>='
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubyforge_project:
128
+ rubygems_version: 1.8.24
129
+ signing_key:
130
+ specification_version: 3
131
+ summary: Build ruby apps
132
+ test_files:
133
+ - spec/acceptance/acceptance_spec.rb
134
+ - spec/acceptance/templates/override.html.erb
135
+ - spec/acceptance/templates/test_router/override.html.erb
136
+ - spec/acceptance/templates/test_template.html.erb
137
+ - spec/acceptance/test_app.rb
138
+ - spec/knot/dispatch/filter_spec.rb
139
+ - spec/knot/dispatch/path_spec.rb
140
+ - spec/knot/dispatch/response_spec.rb
141
+ - spec/knot/dispatch/route_spec.rb
142
+ - spec/knot/dispatch/router/config_spec.rb
143
+ - spec/knot/dispatch/router_spec.rb
144
+ - spec/knot/render/binding_spec.rb
145
+ - spec/knot/render_spec.rb
146
+ - spec/knot/util_spec.rb