mwmitchell-snap 0.5.0 → 0.6.0

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/LICENSE CHANGED
@@ -1,5 +1,5 @@
1
1
  Copyright (c) 2008 Matt Mitchell - goodieboy@gmail.com
2
-
2
+
3
3
  Permission is hereby granted, free of charge, to any person
4
4
  obtaining a copy of this software and associated documentation
5
5
  files (the "Software"), to deal in the Software without
@@ -8,10 +8,10 @@ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
8
  copies of the Software, and to permit persons to whom the
9
9
  Software is furnished to do so, subject to the following
10
10
  conditions:
11
-
11
+
12
12
  The above copyright notice and this permission notice shall be
13
13
  included in all copies or substantial portions of the Software.
14
-
14
+
15
15
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
16
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
17
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
@@ -19,4 +19,4 @@ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
19
  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
20
  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
21
  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
- OTHER DEALINGS IN THE SOFTWARE.
22
+ OTHER DEALINGS IN THE SOFTWARE.
data/README CHANGED
@@ -1,132 +1,59 @@
1
- =Snap
1
+ = Snap - REST made easy
2
2
 
3
- Snap is a ruby framework for creating `RESTful` web applications. Using Rack: http://rack.rubyforge.org - "Rack: a Ruby Webserver Interface", it's designed to be flexible, fast and easy to understand.
4
-
5
- ==Examples
6
-
7
- # Simple GET/POST handler for /
8
- class GetPost
9
- extend Snap::App
10
- map do
11
- get{}
12
- post{}
13
- end
14
- end
3
+ == Overview
4
+ Snap is a small web framework for creating RESTful web applications. The heart of Snap is the controller layer; a contextual, block based DSL featuring RESTful action hooks, an event filter system and url param mapping.
15
5
 
16
- # Nested GET/POST handler for /contact
17
- class GetPost
18
- extend Snap::App
19
- map do
20
- map :contact do
21
- get{}
22
- post{}
23
- end
24
- end
25
- end
6
+ The start of any Snap application is a root Context. Contexts are sub-slices of a url and help keep routes DRY. The Context is responsible for defining the base URL path for it's actions, filters and child Contexts. One Context is equal to a set of URL path fragments. For example, a context could be /admin/transactions. Within that Context, there can be multiple actions, filters and sub-contexts.
26
7
 
27
- ==Logic Flow
8
+ The request and before/after handlers are able to negotiate different formats of content using the :format method within an action block.
28
9
 
29
- ================================================================
10
+ Snap is very similar to Sinatra in that each action is a simple block. Snap is a little more structured and flexible than Sinatra. This can be a disadvantage or an advantage depending on the type of app you're trying to build.
30
11
 
31
- Snap is a Ruby, DSL based framework for creating RESTful web applications. It adheres to the Ruby Rack specification.
32
-
33
- Snap treats every fragment in a URL as a "context". Each context can be mapped to a block or another Snap enabled module. For example, a URL of:
34
-
35
- <pre>/users/1/images/1</pre>
36
-
37
- could translate to a context block tree of:
38
-
39
- <pre>
40
- <code>
41
- map 'users' do
42
- map :digit do
43
- get{"The value of this user is #{value}"}
44
- map 'images' do
45
- map :digit do
46
- get{"The ID of this image is #{value}"}
47
- end
48
- end
49
- end
50
- end
51
- </code>
52
- </pre>
53
-
54
- Because each block maps to a single url fragment, "routing" is not needed. The value of a context's url fragment can be accessed by its :value attribute. You can also name the fragment and propagate to the request params like:
55
-
56
- <pre>
57
- <code>
58
- map :user_id=>/\d+/ do
59
- puts params[:user_id]
60
- end
61
- </code>
62
- </pre>
63
-
64
- The :user_id param will then be available as a global request param to other contexts and "actions".
65
-
66
- The fragment value passed to :map can be a string, a regular expression or a symbol that matches one of the built-in rules; :digit, :word, etc.
67
-
68
- Standard HTTP methods (GET, POST, PUT, etc.) are used as the "actions". Each of the actions methods can accept a "rule" hash. The keys map to the REQUEST values (but down-cased and symbolized). So you could execute an action only if it matched the IP, or SERVER_NAME. The value of the rule can be a Regular Expression or a string.
69
-
70
- Here is an example:
71
-
72
- <pre>
73
- <code>
74
- require 'snap'
75
-
76
- module SeriousApp
77
-
78
- class Contact # A delegate/sub-app
79
-
80
- extend Snap::App
81
-
82
- build do
83
- before :post do
84
- # a before :post block. Default is :all.
85
- end
86
-
87
- get{'GET request'}
88
- post{'POST request'}
89
-
90
- # request for /contact/:word
91
- map :word do
92
- get{"GET request for /contact/#{value}"}
93
- end
94
- end
95
- end
96
-
97
- class Root
98
-
99
- extend Snap::App
100
-
101
- build do
102
- get{'A simple GET to /'}
103
- post{'A POST to /'}
104
-
105
- # /about/
106
- map 'about' do
107
- get{'GET request for /about'}
108
- end
109
-
110
- # /notes
111
- map 'notes' do
112
- get(:server_name=>/^myspecialdomain\.com$/) do
113
- 'GET request for /notes, but only allowing a SERVER_NAME of "myspecialdomain.com"'}
114
- end
115
- end
116
-
117
- # forwarding /contact to the Contact class
118
- map 'contact' { use Contact }
119
- end
120
-
121
- end
122
- end
123
- </code>
124
- </pre>
125
-
126
- You then instantiate the Root class, and pass it to Rack's "run" method.
12
+ * Thanks to Merb and Sinatra for the little bits of code here and there
13
+
14
+ == Example
15
+ Here is a simple example:
16
+
17
+ module SimpleApp; end
127
18
 
128
- <pre>
129
- <code>
130
- run SeriousApp::Root.new
131
- </code>
132
- </pre>
19
+ class SimpleApp::Root
20
+
21
+ include Snap::App
22
+
23
+ map do
24
+
25
+ after do
26
+ nav = <<-EOF
27
+ <ul>
28
+ <li><a href="/">home</a></li>
29
+ <li><a href="/about">about</a></li>
30
+ <li><a href="/about/ruby">about ruby</a></li>
31
+ <li><a href="/markaby">markaby example</a></li>
32
+ </ul>
33
+ EOF
34
+ response.body = "<h1>Snap!</h1><hr/>#{nav}#{response.body}<hr/>"
35
+ end
36
+
37
+ get {'The root path using GET'}
38
+
39
+ get :markaby=>'markaby' do
40
+ markaby.div do
41
+ h3 "Render your HTML with Markaby?"
42
+ ul do
43
+ li "markaby is cool"
44
+ li "It's fun to put view logic in your controllers :)"
45
+ li "testing..."
46
+ end
47
+ end
48
+ end
49
+
50
+ map 'about' do
51
+ get {"Snap is an experimental framework?!"}
52
+ get 'ruby' do
53
+ 'Ruby -> http://www.ruby-lang.org'
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ end
data/bin/snap ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'rake'
4
+ Rake.application.run
data/lib/core_ext.rb ADDED
@@ -0,0 +1,108 @@
1
+ class Symbol
2
+ def to_proc
3
+ proc { |obj, *args| obj.send(self, *args) }
4
+ end
5
+ end
6
+
7
+ class Proc
8
+
9
+ def source
10
+ self.to_s.scan(/@(.*)>/)
11
+ end
12
+
13
+ end
14
+
15
+ class String
16
+
17
+ # Converts +self+ to an escaped URI parameter value
18
+ # 'Foo Bar'.to_param # => 'Foo%20Bar'
19
+ def to_param
20
+ URI.escape(self)
21
+ end
22
+
23
+ # Converts +self+ from an escaped URI parameter value
24
+ # 'Foo%20Bar'.from_param # => 'Foo Bar'
25
+ def from_param
26
+ URI.unescape(self)
27
+ end
28
+
29
+ #
30
+ # :trim and :trim!
31
+ # Similar to PHP's trim function
32
+ # Aceepts multiple arguments for characters to trim
33
+ # from the beginning of the string and the end
34
+ #
35
+ %W(trim trim!).each do |m|
36
+ define_method m do |*args|
37
+ raise 'Characters argument missing' unless (chars=args.join(' '))
38
+ p = [/^[#{Regexp.escape(chars)}]+|[#{Regexp.escape(chars)}]+$/, '']
39
+ m[-1..-1] == '!' ? self.gsub!(*p) : self.gsub(*p)
40
+ end
41
+ end
42
+
43
+ #
44
+ # Removes duplicate instances of characters
45
+ #
46
+ %W(dedup dedup!).each do |m|
47
+ define_method m do |*args|
48
+ raise 'Character argument missing' if args.nil?
49
+ value=self
50
+ args.each do |char|
51
+ p = [/#{Regexp.escape(char)}+/, char]
52
+ m[-1..-1] == '!' ? self.gsub!(*p) : (value = value.gsub(*p))
53
+ end
54
+ value
55
+ end
56
+ end
57
+
58
+ #
59
+ # removes starting and ending instances of the characters argument (calls :trim)
60
+ # replaces sets of duplicated characters with a single character (calls :dedup)
61
+ #
62
+ # puts 'testtktu'.cleanup('t', 'u') == 'estk'
63
+ #
64
+ %W(cleanup cleanup!).each do |m|
65
+ define_method m do |*args|
66
+ m[-1..-1] == '!' ? (trim!(*args) && dedup!(*args)) : (trim(*args).dedup(*args))
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ class Hash
73
+
74
+ def deep_merge(hash)
75
+ target = dup
76
+ hash.keys.each do |key|
77
+ if hash[key].is_a? Hash and self[key].is_a? Hash
78
+ target[key] = target[key].deep_merge(hash[key])
79
+ next
80
+ end
81
+ target[key] = hash[key]
82
+ end
83
+ target
84
+ end
85
+
86
+ def deep_merge!(second)
87
+ second.each_pair do |k,v|
88
+ if self[k].is_a?(Hash) and second[k].is_a?(Hash)
89
+ self[k].deep_merge!(second[k])
90
+ else
91
+ self[k] = second[k]
92
+ end
93
+ end
94
+ end
95
+
96
+ def to_params
97
+ map { |k,v| "#{k}=#{URI.escape(v)}" }.join('&')
98
+ end
99
+
100
+ def symbolize_keys
101
+ self.inject({}) { |h,(k,v)| h[k.to_sym] = v; h }
102
+ end
103
+
104
+ def pass(*keys)
105
+ reject { |k,v| !keys.include?(k) }
106
+ end
107
+
108
+ end
data/lib/snap.rb CHANGED
@@ -1,27 +1,32 @@
1
- $:.delete_if {|v|v=~/TextMate/}
2
-
3
- $:.unshift File.dirname(__FILE__) unless
4
- $:.include?(File.dirname(__FILE__)) or $:.include?(File.expand_path(File.dirname(__FILE__)))
1
+ lambda do |p|
2
+ $:.unshift p unless
3
+ $:.include?(p) or $:.include?(File.expand_path(p))
4
+ end.call(File.dirname(__FILE__))
5
5
 
6
6
  require 'rubygems'
7
7
  require 'rack'
8
+ require 'core_ext'
8
9
 
9
- class Proc
10
+ module Snap
10
11
 
11
- #
12
- # grab the file and line number from to_s
13
- #
14
- def source
15
- @source ||= self.to_s.scan(/@(.*:\d+)\>/)
12
+ module Version
13
+ MAJOR = '0'
14
+ MINOR = '4'
15
+ REVISION = '1'
16
+ def self.combined
17
+ [MAJOR, MINOR, REVISION].join('.')
18
+ end
16
19
  end
17
20
 
18
21
  end
19
22
 
20
- module Snap
21
- autoload :App, 'snap/app'
22
- autoload :Context, 'snap/context'
23
- autoload :Request, 'snap/request'
24
- autoload :Demo, 'snap/demo'
25
- autoload :Config, 'snap/config'
26
- autoload :HashBlock, 'snap/hash_block'
27
- end
23
+ %W(
24
+ config
25
+ context
26
+ initializer
27
+ response_helpers
28
+ request
29
+ zone
30
+ view_helper
31
+ app
32
+ ).each{|p|require "snap/#{p}"}
data/lib/snap/context.rb CHANGED
@@ -1,9 +1,23 @@
1
1
  #
2
- #
2
+ # The "Context" is essentially the current request
3
3
  #
4
4
  module Snap::Context
5
- autoload :Base, 'snap/context/base'
6
- autoload :Events, 'snap/context/events'
7
- autoload :Matcher, 'snap/context/matcher'
8
- autoload :Actions, 'snap/context/actions'
5
+
6
+ # these will be accessable thru Snap::Context
7
+ M=%W(request response config)
8
+
9
+ # class instance attributes (ex. Snap::Context.config etc.)
10
+ class << self
11
+ M.each{|meth| attr_accessor meth}
12
+ end
13
+
14
+ # This makes the above methods available to the classes that "include Snap::Context"
15
+ M.each do |meth|
16
+ class_eval <<-EOF
17
+ def #{meth}(*args)
18
+ Snap::Context.send(:#{meth}, *args)
19
+ end
20
+ EOF
21
+ end
22
+
9
23
  end
data/lib/snap/request.rb CHANGED
@@ -1,11 +1,50 @@
1
1
  class Snap::Request < Rack::Request
2
2
 
3
- def m
4
- request_method.to_s.downcase.to_sym
3
+ def initialize(env)
4
+ super(env)
5
+ pi=env['PATH_INFO'].to_s.split('.')
6
+ @path_info=pi.first.cleanup('/')
7
+ @format=pi.size==2 ? pi.last.to_sym : default_format
8
+ @request_method=env['REQUEST_METHOD'].downcase.to_sym
9
+ if post_tunnel_method_hack?
10
+ @request_method = params['_method'].downcase.to_sym
11
+ end
5
12
  end
6
13
 
7
- def path_info_slices
8
- ['/'] + path_info.to_s.gsub(/\/+/, '/').sub(/^\/|\/$/, '').split('/')
14
+ def default_format; :html end
15
+
16
+ def format
17
+ @format
18
+ end
19
+
20
+ def path_info
21
+ @path_info
9
22
  end
10
23
 
24
+ # Set of request method names allowed via the _method parameter hack. By default,
25
+ # all request methods defined in RFC2616 are included, with the exception of
26
+ # TRACE and CONNECT.
27
+ POST_TUNNEL_METHODS_ALLOWED = [:put, :delete, :options, :head]
28
+
29
+ # Return the HTTP request method with support for method tunneling using the POST
30
+ # _method parameter hack. If the real request method is POST and a _method param is
31
+ # given and the value is one defined in +POST_TUNNEL_METHODS_ALLOWED+, return the value
32
+ # of the _method param instead.
33
+ def request_method
34
+ @request_method
35
+ end
36
+
37
+ private
38
+
39
+ # Return truthfully if and only if the following conditions are met: 1.) the
40
+ # *actual* request method is POST, 2.) the request content-type is one of
41
+ # 'application/x-www-form-urlencoded' or 'multipart/form-data', 3.) there is a
42
+ # "_method" parameter in the POST body (not in the query string), and 4.) the
43
+ # method parameter is one of the verbs listed in the POST_TUNNEL_METHODS_ALLOWED
44
+ # list.
45
+ def post_tunnel_method_hack?
46
+ @request_method == :post &&
47
+ POST_TUNNEL_METHODS_ALLOWED.include?(self.POST.fetch('_method', '').downcase.to_sym)
48
+ end
49
+
11
50
  end
@@ -0,0 +1,89 @@
1
+ module Snap::ResponseHelpers
2
+
3
+ class FormatMapper
4
+
5
+ def initialize
6
+ @formats={}
7
+ end
8
+
9
+ def method_missing(m,&block)
10
+ # check with mime-types here...
11
+ @formats[m]=block
12
+ end
13
+
14
+ def resolve(format)
15
+ @formats[format]
16
+ end
17
+
18
+ end
19
+
20
+ def format_mapper
21
+ @format_mapper||=FormatMapper.new
22
+ end
23
+
24
+ def format
25
+ format_mapper
26
+ end
27
+
28
+ def redirect(path, *args)
29
+ response.status=302
30
+ headers 'Location' => path
31
+ throw :halt, *args
32
+ end
33
+
34
+ def stop(*args)
35
+ throw :halt, args
36
+ end
37
+
38
+ def response
39
+ @response
40
+ end
41
+
42
+ def headers(header = nil)
43
+ response.headers.merge!(header) if header
44
+ response.headers
45
+ end
46
+
47
+ alias :header :headers
48
+
49
+ def render(template=nil, variables={}, options={})
50
+ options[:renderer]||=config.default_renderer
51
+ m = "render_#{options[:renderer]}"
52
+ config.view_paths.each do |p|
53
+ template = File.read(File.join(p, "#{template}.#{request.format}.erb")) rescue nil
54
+ end
55
+ render_erb([self], erb_helpers, template, variables)
56
+ end
57
+
58
+ def erb_helpers
59
+ [Snap::ViewHelper::Erubis]
60
+ end
61
+
62
+ def render_erb(variable_bindings, extensions, template, data={})
63
+ variable_bindings.each do |o|
64
+ o.instance_variables.each do |k|
65
+ data[k[1..-1]]=o.instance_variable_get(k)
66
+ end
67
+ end
68
+ context=Object.new
69
+ extensions.each do |e|
70
+ context.extend e
71
+ end
72
+ data.each do |k,v|
73
+ context.instance_variable_set("@#{k}", v)
74
+ end
75
+ require 'erubis_ext'
76
+ ::Erubis::BlockAwareEruby.new(nil, :trim=>false).process(template, context)
77
+ end
78
+
79
+ def render_haml(content, options = {}, &b)
80
+ require 'haml'
81
+ ::Haml::Engine.new(content).render(options[:scope] || self, options[:locals] || {}, &b)
82
+ end
83
+
84
+ def markaby
85
+ require 'markaby'
86
+ Markaby::Builder.new
87
+ end
88
+
89
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mwmitchell-snap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Mitchell
@@ -9,7 +9,7 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
 
12
- date: 2008-06-21 00:00:00 -07:00
12
+ date: 2008-08-06 00:00:00 -07:00
13
13
  default_executable:
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
@@ -19,9 +19,9 @@ dependencies:
19
19
  requirements:
20
20
  - - "="
21
21
  - !ruby/object:Gem::Version
22
- version: 0.3.0
22
+ version: 0.4.0
23
23
  version:
24
- description: Snap is a Ruby, DSL based framework for creating RESTful web applications.
24
+ description: Snap - a minimalist framework for creating RESTful web applications
25
25
  email: goodieboy@gmail.com
26
26
  executables: []
27
27
 
@@ -33,17 +33,20 @@ extra_rdoc_files:
33
33
  files:
34
34
  - README
35
35
  - LICENSE
36
- - CHANGES
37
- - examples/demo.rb
36
+ - bin/snap
37
+ - lib/core_ext.rb
38
38
  - lib/snap.rb
39
- - lib/snap/app.rb
39
+ - lib/snap/action.rb
40
+ - lib/snap/context/action_methods.rb
41
+ - lib/snap/context/hooks.rb
40
42
  - lib/snap/context.rb
41
- - lib/snap/demo.rb
43
+ - lib/snap/event.rb
44
+ - lib/snap/loader.rb
45
+ - lib/snap/negotiator.rb
46
+ - lib/snap/rack_runner.rb
47
+ - lib/snap/renderers.rb
42
48
  - lib/snap/request.rb
43
- - lib/snap/context/base.rb
44
- - lib/snap/context/actions.rb
45
- - lib/snap/context/events.rb
46
- - lib/snap/context/matcher.rb
49
+ - lib/snap/response_helpers.rb
47
50
  has_rdoc: true
48
51
  homepage: http://github.com/mwmitchell/snap
49
52
  post_install_message:
@@ -70,6 +73,6 @@ rubyforge_project:
70
73
  rubygems_version: 1.2.0
71
74
  signing_key:
72
75
  specification_version: 2
73
- summary: Snap is a Ruby, DSL based framework for creating RESTful web applications.
76
+ summary: Snap - a minimalist framework for creating RESTful web applications
74
77
  test_files: []
75
78
 
data/CHANGES DELETED
File without changes
data/examples/demo.rb DELETED
@@ -1,84 +0,0 @@
1
- require File.join(File.dirname(__FILE__), '..', 'lib', 'snap')
2
-
3
- app=Snap::Demo::Controllers::Root.new
4
-
5
- rack_env=Rack::MockRequest.env_for('/admin/customers', {'REQUEST_METHOD'=>'GET', 'SERVER_PORT'=>'80'})
6
- puts app.call(rack_env)
7
-
8
- puts "
9
- ...
10
-
11
- "
12
-
13
-
14
- rack_env=Rack::MockRequest.env_for('/admin/customers/henry', {'REQUEST_METHOD'=>'PUT', 'SERVER_PORT'=>'80'})
15
- puts app.call(rack_env)
16
-
17
- puts "
18
- ...
19
-
20
- "
21
-
22
-
23
-
24
- rack_env=Rack::MockRequest.env_for('/', {'REQUEST_METHOD'=>'GET', 'SERVER_PORT'=>'80'})
25
- puts app.call(rack_env)
26
-
27
- puts "
28
- ...
29
-
30
- "
31
-
32
- rack_env=Rack::MockRequest.env_for('/admin', {'REQUEST_METHOD'=>'GET', 'SERVER_PORT'=>'80'})
33
- puts app.call(rack_env)
34
-
35
- puts "
36
- ...
37
-
38
- "
39
-
40
- rack_env=Rack::MockRequest.env_for('/admin/orders', {'REQUEST_METHOD'=>'POST', 'SERVER_PORT'=>'80'})
41
- puts app.call(rack_env)
42
-
43
- puts "
44
- ...
45
-
46
- "
47
-
48
-
49
- rack_env=Rack::MockRequest.env_for('/admin/products', {'REQUEST_METHOD'=>'GET', 'SERVER_PORT'=>'80'})
50
- puts app.call(rack_env)
51
-
52
- puts "
53
- ...
54
-
55
- "
56
-
57
- rack_env=Rack::MockRequest.env_for('/contact', {'REQUEST_METHOD'=>'GET', 'SERVER_PORT'=>'80'})
58
- puts app.call(rack_env)
59
-
60
- puts "
61
- ...
62
-
63
- "
64
-
65
- rack_env=Rack::MockRequest.env_for('/contact/the-boss', {'REQUEST_METHOD'=>'GET', 'SERVER_PORT'=>'80'})
66
- puts app.call(rack_env)
67
-
68
- puts "
69
- ...
70
-
71
- "
72
-
73
- rack_env=Rack::MockRequest.env_for('/admin/products/144/edit', {'REQUEST_METHOD'=>'GET', 'SERVER_PORT'=>'80'})
74
- puts app.call(rack_env)
75
-
76
-
77
-
78
- puts "
79
- ...
80
-
81
- "
82
-
83
- rack_env=Rack::MockRequest.env_for('/admin/products/144', {'REQUEST_METHOD'=>'GET', 'SERVER_PORT'=>'80'})
84
- puts app.call(rack_env)
data/lib/snap/app.rb DELETED
@@ -1,56 +0,0 @@
1
- #
2
- #
3
- #
4
-
5
- require 'monitor'
6
-
7
- module Snap::App
8
-
9
- attr :root
10
-
11
- #
12
- #
13
- #
14
- def map(options={}, &block)
15
- @root=Snap::Context::Base.new('/', options, nil, &block)
16
- end
17
-
18
- #
19
- #
20
- #
21
- def self.extended(base)
22
-
23
- base.class_eval do
24
-
25
- #
26
- # Provides sync functionality when using Threads and shared resources
27
- #
28
- include MonitorMixin
29
-
30
- attr_accessor :config
31
- attr_accessor :request
32
- attr_accessor :response
33
-
34
- #
35
- #
36
- #
37
- def call(rack_env)
38
- synchronize do
39
- @request = Snap::Request.new(rack_env)
40
- @response = Rack::Response.new
41
- c=self.class.root.resolve(@request.path_info_slices, self)
42
- result = c.execute if c
43
- response.finish
44
- end
45
- end
46
-
47
- end
48
- end
49
-
50
- class Base
51
-
52
- extend Snap::App
53
-
54
- end
55
-
56
- end
@@ -1,64 +0,0 @@
1
- module Snap::Context::Actions
2
-
3
- #
4
- #
5
- #
6
- def index(options={}, &block)
7
- get(options, &block)
8
- end
9
-
10
- #
11
- #
12
- #
13
- def create(options={}, &block)
14
- put(options, &block)
15
- end
16
-
17
- #
18
- #
19
- #
20
- def new(options={}, &block)
21
- map(:new) do
22
- get(options, &block)
23
- end
24
- end
25
-
26
- #
27
- #
28
- #
29
- def show(pattern=:digit, options={}, &block)
30
- map(pattern) do
31
- get(options, &block)
32
- end
33
- end
34
-
35
- #
36
- #
37
- #
38
- def update(pattern=:digit, options={}, &block)
39
- map(pattern) do
40
- put(options, &block)
41
- end
42
- end
43
-
44
- #
45
- #
46
- #
47
- def delete(pattern=:digit, options={}, &block)
48
- map(pattern) do
49
- delete(options, &block)
50
- end
51
- end
52
-
53
- #
54
- #
55
- #
56
- def edit(pattern={:id=>:digit}, options={}, &block)
57
- map(pattern) do
58
- map(:edit) do
59
- get(options, &block)
60
- end
61
- end
62
- end
63
-
64
- end
@@ -1,180 +0,0 @@
1
- ###
2
- class Snap::Context::Base
3
-
4
- include Snap::Context::Events
5
- include Snap::Context::Matcher
6
-
7
- attr :pattern
8
- attr :options
9
- attr_accessor :parent
10
- attr :block
11
- attr :app
12
- attr :slices
13
- attr :value
14
-
15
- def initialize(pattern, options={}, parent=nil, &block)
16
- @pattern=pattern
17
- @options=options
18
- @parent=parent
19
- @block=block
20
- end
21
-
22
- [:get,:post,:put,:delete].each do |m|
23
- class_eval <<-EOF
24
- def #{m}(options={},&block)
25
- # don't add the action if the block is the same as existing action block
26
- unless actions.detect{|a|a[2].source==block.source}
27
- actions << [:#{m},options,block]
28
- end
29
- end
30
- EOF
31
- end
32
-
33
- def app; @app end
34
- def request; app.request end
35
- def response; app.response end
36
- def params; request.params end
37
- def actions; @actions||=[] end
38
- def children; @children||=[] end
39
-
40
- #
41
- # builds the url path to this context
42
- #
43
- def path
44
- @path ||= ((parent ? parent.path : '') + '/' + value).gsub(/\/+/, '/')
45
- end
46
-
47
- #
48
- # +use+ is the method that allows context to delegate
49
- # to other contexts, alloing logic to be off-loaded
50
- # to other classes. The klass argument
51
- # must be the class name of an object that is
52
- # using "extend Snap::App"
53
- #
54
- def use(klass,*args,&block)
55
- usable_app=klass.new(*args,&block)
56
- usable_app.class.root.parent=self
57
- children<<usable_app
58
- end
59
-
60
- #
61
- # Define a new sub-context block
62
- # pattern can be a string, a symbol or a hash
63
- #
64
- def map(pattern,options={},&block)
65
- # don't add the context if the block is the same as existing action block
66
- return if children.detect{|c|c.block.source == block.source}
67
- children<<self.class.new(pattern,options,self,&block)
68
- end
69
-
70
- #
71
- #
72
- #
73
- def find_action(method, env={})
74
- actions.detect{ |a| a[0] == method and options_match?(a[1], env)}
75
- end
76
-
77
- #
78
- #
79
- #
80
- def can?(*args)
81
- ! find_action(*args).nil?
82
- end
83
-
84
- #
85
- #
86
- #
87
- def method_missing(m,*a,&block)
88
- puts "METHOD MISSING #{m.inspect}"
89
- puts "APP IS #{@app} (should not be a context::base)"
90
- @app.send(m,*a,&block)
91
- end
92
-
93
- #
94
- #
95
- #
96
- def execute(method=request.m, env=request.env)
97
- a=find_action(method,env)
98
- result=nil
99
- unless a.nil?
100
- execute_before_and_after_blocks(method, env){result=instance_eval &a[2]}
101
- end
102
- result
103
- end
104
-
105
- #
106
- #
107
- #
108
- def resolve(slices, app)
109
- @app=app
110
-
111
- method=app.request.m
112
- env=app.request.env
113
-
114
- #
115
- # must clone the slices; in some cases we need to climb back up the context stack
116
- # for example, if two contexts are defined with the same pattern
117
- # but the first one doesn't define a matching action block,
118
- # we need to be able to move back up to get the second one.
119
- # if the slices aren't cloned, then the 2nd one
120
- # would get a wrongly manipulated slices array - oh no!
121
- #
122
- slices=slices.clone
123
-
124
- # try to match the @pattern with the given slices
125
- # then extract the value
126
- val = match?(@pattern,slices.first)
127
-
128
- return unless val
129
-
130
- #
131
- # now make sure that the options can match the env
132
- #
133
- return unless options_match?(@options, env)
134
-
135
- # if the returned value was a hash, merge it into the apps request.param hash
136
- app.request.params.merge!(val) if val.is_a?(Hash)
137
-
138
- #
139
- # shift off the first item, save it as the value
140
- # of this context this is needed so that the
141
- # next context can have the next
142
- # value in the slices array
143
- #
144
- @value=slices.shift
145
-
146
- @slices=slices
147
-
148
- result = catch :halt do
149
- instance_eval &@block
150
- end
151
-
152
- #
153
- # "and can?(method, env)" -> This seemed to allow falling back to an action that is a parent
154
- #
155
- return self if slices.empty?# and can?(method, env)
156
-
157
- matching_child=nil
158
- children.each do |c|
159
- if c.is_a?(self.class)
160
- # standard Snap::Context::Base instance set by +map+
161
- matching_child=c.resolve(slices, app)
162
- # matching_child.parent=self if matching_child
163
- else
164
- # if this is an object set by the +use+ method
165
- # c is not a Snap::Context::Base; it
166
- # should be a class calling "extend Snap::App"
167
- # (a sort of sub-application)
168
- # so it needs a root slash for it's
169
- # own "root" context...
170
- # set the sub-app's request and response instances
171
- c.request=request
172
- c.response=response
173
- matching_child=c.class.root.resolve(['/']+slices,c)
174
- end
175
- return matching_child if matching_child
176
- end
177
- nil
178
- end
179
-
180
- end
@@ -1,43 +0,0 @@
1
- #
2
- #
3
- #
4
- module Snap::Context::Events
5
-
6
- include Snap::Context::Matcher
7
-
8
- def before_blocks; @before_blocks||={} end
9
- def after_blocks; @after_blocks||={} end
10
-
11
- def before(action=:all, options={}, &block)
12
- before_blocks[action]=[options,block]
13
- end
14
-
15
- def after(action=:all, options={}, &block)
16
- after_blocks[action]=[options,block]
17
- end
18
-
19
- def execute_before_and_after_blocks(method, options, &block)
20
- execute_before_blocks method, options
21
- yield
22
- execute_after_blocks method, options
23
- end
24
-
25
- #
26
- #
27
- #
28
- def execute_before_blocks(method, options={})
29
- parent.execute_before_blocks(method, options) if parent
30
- b=(@before_blocks[method] || @before_blocks[:all]) if @before_blocks
31
- instance_eval(&b[1]) if b and options_match?(b[0], options)
32
- end
33
-
34
- #
35
- #
36
- #
37
- def execute_after_blocks(method, options={})
38
- b=(@after_blocks[method] || @after_blocks[:all]) if @after_blocks
39
- instance_eval(&b[1]) if b and options_match?(b[0], options)
40
- parent.execute_after_blocks(method, options) if parent
41
- end
42
-
43
- end
@@ -1,99 +0,0 @@
1
- =begin
2
-
3
- # TEST
4
-
5
- include Snap::Context::Matcher
6
-
7
- puts true == options_match?({}, {:server_name=>'localhost'})
8
-
9
- puts true ==options_match?({:server_name=>'localhost'}, {:server_name=>'localhost'})
10
-
11
- puts true == options_match?({:server_name=>/local/}, {:server_name=>'localhost'})
12
-
13
- puts false == options_match?({:server_name=>/k/}, {:server_name=>'localhost'})
14
-
15
- puts false == options_match?({:asdasd=>'x', :localhost=>'localhost'}, {:server_name=>'localhost'})
16
-
17
- puts true == options_match?({}, {:server_name=>'localhost', :port=>8080})
18
-
19
- puts false == options_match?({:port=>9}, {:port=>8080})
20
-
21
- =end
22
-
23
- #
24
- #
25
- #
26
- module Snap::Context::Matcher
27
-
28
- PATTERNS={
29
- :digit=>/^\d+$/,
30
- :word=>/^\w+$/
31
- }
32
-
33
- #
34
- # Checks the pattern against the value
35
- # The pattern can be a:
36
- # * string - uses == to compare
37
- # * regexp - uses =~ to compare
38
- # * symbol - first looks up value in PATTERNS; if found, uses the PATTERN value; if not, converts to string
39
- # * hash - assigns the value to the key within the params array if matches; the value can be any of the above
40
- #
41
- # ==Examples
42
- # match?('hello', 'hello') == true
43
- # match?(/h/, 'hello') == true
44
- # match?(:digit, 1) == true - does a successfull lookup in PATTERNS
45
- # match?(:admin, 'admin') == true - non-succesfull PATTERNS lookup, convert to string
46
- # match?(:id=>:digit, 1) == true - also sets params[:id]=1
47
- # match?(:name=>:word, 'sam') == true - sets params[:name]='sam'
48
- # match?(:topic=>/^authors$|^publishers$/, 'publishers') - true - sets params[:topic]='publishers'
49
- #
50
- def match?(pattern, value)
51
- ok = simple_match?(pattern, value)
52
- return ok if ok
53
- # if hash, use the value as the pattern and set the request param[key] to the value argument
54
- return ({pattern.keys.first => match?(pattern[pattern.keys.first], value)}) if pattern.is_a?(Hash)
55
- # if symbol
56
- if pattern.is_a?(Symbol)
57
- # lookup the symbol in the PATTERNS hash
58
- if PATTERNS[pattern] and new_value=match?(PATTERNS[pattern], value)
59
- return new_value
60
- end
61
- # There was no match, convert to string
62
- simple_match?(pattern.to_s, value)
63
- end
64
- end
65
-
66
- #
67
- # Non-nested comparisons using == or =~
68
- #
69
- def simple_match?(pattern, value)
70
- # if regexp, regx comparison
71
- return value if (pattern.is_a?(Regexp) and value.to_s =~ pattern)
72
- # if string, simple comparison
73
- return value if (pattern.is_a?(String) and value.to_s == pattern)
74
- end
75
-
76
- #
77
- #
78
- #
79
- def options_match?(compare_from, compare_to)
80
- if compare_from.to_s.empty?
81
- return true
82
- end
83
- n={}
84
- compare_to.each_pair{ |k,v| n[k.to_s.downcase.gsub('-','_').gsub(' ', '').gsub('.', '_').to_sym]=v }
85
- matches=true
86
- compare_from.each_pair do |k,v|
87
- unless n.has_key?(k)
88
- matches=false
89
- break
90
- end
91
- unless match?(v,n[k]) or match?(v.to_s,n[k])
92
- matches=false
93
- break
94
- end
95
- end
96
- matches
97
- end
98
-
99
- end
data/lib/snap/demo.rb DELETED
@@ -1,169 +0,0 @@
1
- module Snap::Demo
2
-
3
- module Controllers
4
-
5
- module Admin
6
-
7
- class Customers
8
-
9
- extend Snap::App
10
-
11
- map do
12
-
13
- extend Snap::Context::Actions
14
-
15
- index do
16
- 'Admin::Customers INDEX /admin/customers -> ' + path
17
- end
18
-
19
- update :customer_name=>/\w+/ do
20
- app.response.write 'TESTING'
21
- "Admin::Customers UPDATE /admin/customers/#{params[:customer_name]} -> " + path
22
- end
23
-
24
- end
25
-
26
- end
27
-
28
- class Products
29
-
30
- extend Snap::App
31
-
32
- map do
33
-
34
- extend Snap::Context::Actions
35
-
36
- before do
37
- puts "Admin::Products BEFORE"
38
- end
39
-
40
- after do
41
- puts "Admin::Products AFTER"
42
- end
43
-
44
- get{'Admin::Products GET /admin/products -> ' + path}
45
-
46
- map :product_id=>:digit do
47
- get do
48
- out = "Admin::Products GET /admin/products/#{params[:product_id]} -> #{path}"
49
- out += "\nThe parent path is #{parent.path}"
50
- end
51
- end
52
-
53
- edit do
54
- out = "Admin::Products EDIT /admin/products/#{params[:id]}/edit -> " + path
55
- out += "\nThe parent path is #{parent.path}"
56
- end
57
-
58
- index do
59
- 'Admin::Products INDEX /admin/products'
60
- end
61
-
62
- new :server_name=>/example/ do
63
- 'Admin::Products NEW /admin/products'
64
- end
65
-
66
- end
67
- end
68
-
69
- end
70
-
71
- class Contact
72
-
73
- extend Snap::App
74
-
75
- map do
76
-
77
- before{puts "Contact BEFORE"}
78
- after{puts "Contact AFTER"}
79
-
80
- get{'Contact GET /'}
81
-
82
- map 'the-boss' do
83
- before do
84
- puts 'Contact BEFORE /the-boss'
85
- end
86
- after do
87
- puts 'Contact AFTER /the-boss'
88
- end
89
- get{'Contact GET /the-boss -> ' + path}
90
- end
91
-
92
- end
93
- end
94
-
95
- class Root
96
-
97
- extend Snap::App
98
-
99
- attr_accessor :count
100
-
101
- map do
102
-
103
- before do
104
- puts 'Root BEFORE /'
105
- end
106
-
107
- after do
108
- puts 'Root AFTER /'
109
- end
110
-
111
- get{'GET /'}
112
- post{'POST /'}
113
- put{'PUT /'}
114
- delete{'DELETE /'}
115
-
116
- map :contact do
117
- before do
118
- puts 'Root BEFORE /contact'
119
- end
120
- after do
121
- puts 'Root AFTER /contact'
122
- end
123
- map 'the-boss' do
124
- before :post do
125
- puts 'Root BEFORE-POST /contact/the-boss'
126
- end
127
- after :post do
128
- puts 'Root AFTER-POST /contact/the-boss'
129
- end
130
- post{'Root POST /contact'}
131
- end
132
- use Contact
133
- end
134
-
135
- map :admin do
136
- map(:products){use Admin::Products}
137
- map(:customers){use Admin::Customers}
138
- map :orders do
139
- post{'Root POST /admin/orders'}
140
- end
141
- end
142
-
143
- #
144
- #
145
- #
146
- map :admin do
147
- before do
148
- puts "Root BEFORE /admin"
149
- end
150
- after do
151
- puts "Root AFTER /admin"
152
- end
153
- get{'Root GET /admin'}
154
- map :orders, :server_port=>/^8/ do
155
- get :server_port=>80 do
156
- ':80 GET /admin/orders'
157
- end
158
- get :server_port=>81 do
159
- ':81 GET /admin/orders'
160
- end
161
- end
162
- end
163
- end
164
-
165
- end
166
-
167
- end
168
-
169
- end