deas 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/deas.gemspec CHANGED
@@ -17,6 +17,12 @@ Gem::Specification.new do |gem|
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
19
 
20
- gem.add_development_dependency("assert", ["~> 2.0"])
20
+ gem.add_dependency("ns-options", ["~> 1.0"])
21
+ gem.add_dependency("rack", ["~> 1.5"])
22
+ gem.add_dependency("sinatra", ["~> 1.4"])
23
+
24
+ gem.add_development_dependency("assert", ["~> 2.0"])
25
+ gem.add_development_dependency("assert-mocha", ["~> 1.0"])
26
+ gem.add_development_dependency("rack-test", ["~> 0.6"])
21
27
 
22
28
  end
@@ -0,0 +1,23 @@
1
+ module Deas
2
+
3
+ class RunnerLogger
4
+ attr_reader :summary, :verbose
5
+
6
+ def initialize(logger, verbose = true)
7
+ loggers = [ logger, Deas::NullLogger.new ]
8
+ loggers.reverse! if !verbose
9
+ @verbose, @summary = loggers
10
+ end
11
+
12
+ end
13
+
14
+ class NullLogger
15
+ require 'logger'
16
+
17
+ ::Logger::Severity.constants.each do |name|
18
+ define_method(name.downcase){|*args| } # no-op
19
+ end
20
+
21
+ end
22
+
23
+ end
data/lib/deas/route.rb ADDED
@@ -0,0 +1,45 @@
1
+ require 'deas/sinatra_runner'
2
+
3
+ module Deas
4
+
5
+ class Route
6
+ attr_reader :method, :path, :handler_class_name, :handler_class
7
+
8
+ def initialize(method, path, handler_class_name)
9
+ @method = method
10
+ @path = path
11
+ @handler_class_name = handler_class_name
12
+ @handler_class = nil
13
+ end
14
+
15
+ def constantize!
16
+ @handler_class ||= constantize_name(handler_class_name)
17
+ raise(NoHandlerClassError.new(handler_class_name)) if !@handler_class
18
+ end
19
+
20
+ def runner(sinatra_call)
21
+ Deas::SinatraRunner.new(@handler_class, sinatra_call)
22
+ end
23
+
24
+ private
25
+
26
+ def constantize_name(class_name)
27
+ names = class_name.to_s.split('::').reject{|name| name.empty? }
28
+ klass = names.inject(Object) do |constant, name|
29
+ constant.const_get(name)
30
+ end
31
+ klass == Object ? false : klass
32
+ rescue NameError
33
+ false
34
+ end
35
+
36
+ end
37
+
38
+ class NoHandlerClassError < RuntimeError
39
+ def initialize(handler_class_name)
40
+ super "Deas couldn't find the view handler '#{handler_class_name}'. " \
41
+ "It doesn't exist or hasn't been required in yet."
42
+ end
43
+ end
44
+
45
+ end
@@ -0,0 +1,30 @@
1
+ module Deas
2
+
3
+ class Runner
4
+
5
+ attr_accessor :request, :response, :params, :logger, :session
6
+
7
+ def initialize(handler_class)
8
+ @handler_class = handler_class
9
+ @handler = @handler_class.new(self)
10
+ end
11
+
12
+ def halt(*args)
13
+ raise NotImplementedError
14
+ end
15
+
16
+ def render(*args)
17
+ raise NotImplementedError
18
+ end
19
+
20
+ def redirect(*args)
21
+ raise NotImplementedError
22
+ end
23
+
24
+ def redirect_to(*args)
25
+ raise NotImplementedError
26
+ end
27
+
28
+ end
29
+
30
+ end
@@ -0,0 +1,152 @@
1
+ require 'ns-options'
2
+ require 'ns-options/boolean'
3
+ require 'pathname'
4
+ require 'singleton'
5
+
6
+ require 'deas/logger'
7
+ require 'deas/route'
8
+
9
+ module Deas
10
+
11
+ class Server
12
+ include Singleton
13
+
14
+ class Configuration
15
+ include NsOptions::Proxy
16
+
17
+ # Sinatra based options
18
+ option :env, String, :default => 'development'
19
+ option :root, Pathname, :default => proc{ File.dirname(Deas.config.routes_file) }
20
+
21
+ option :app_file, Pathname, :default => proc{ Deas.config.routes_file }
22
+ option :public_folder, Pathname
23
+ option :views_folder, Pathname
24
+
25
+ option :dump_errors, NsOptions::Boolean, :default => false
26
+ option :method_override, NsOptions::Boolean, :default => true
27
+ option :sessions, NsOptions::Boolean, :default => true
28
+ option :show_exceptions, NsOptions::Boolean, :default => false
29
+ option :static_files, NsOptions::Boolean, :default => true
30
+
31
+ # Deas specific options
32
+ option :init_proc, Proc, :default => proc{ }
33
+ option :logger, :default => proc{ Deas::NullLogger.new }
34
+ option :verbose_logging, :default => true
35
+
36
+ option :routes, Array, :default => []
37
+ option :view_handler_ns, String
38
+
39
+ def initialize
40
+ # these are defaulted here because we want to use the Configuration
41
+ # instance `root`. If we define a proc above, we will be using the
42
+ # Configuration class `root`, which will not update these options as
43
+ # expected.
44
+ super({
45
+ :public_folder => proc{ self.root.join('public') },
46
+ :views_folder => proc{ self.root.join('views') }
47
+ })
48
+ end
49
+
50
+ def runner_logger
51
+ Deas::RunnerLogger.new(self.logger, self.verbose_logging)
52
+ end
53
+ end
54
+
55
+ attr_reader :configuration
56
+
57
+ def initialize
58
+ @configuration = Configuration.new
59
+ end
60
+
61
+ def env(*args)
62
+ self.configuration.env *args
63
+ end
64
+
65
+ def root(*args)
66
+ self.configuration.root *args
67
+ end
68
+
69
+ def public_folder(*args)
70
+ self.configuration.public_folder *args
71
+ end
72
+
73
+ def views_folder(*args)
74
+ self.configuration.views_folder *args
75
+ end
76
+
77
+ def dump_errors(*args)
78
+ self.configuration.dump_errors *args
79
+ end
80
+
81
+ def method_override(*args)
82
+ self.configuration.method_override *args
83
+ end
84
+
85
+ def sessions(*args)
86
+ self.configuration.sessions *args
87
+ end
88
+
89
+ def show_exceptions(*args)
90
+ self.configuration.show_exceptions *args
91
+ end
92
+
93
+ def static_files(*args)
94
+ self.configuration.static_files *args
95
+ end
96
+
97
+ def init(&block)
98
+ self.configuration.init_proc = block
99
+ end
100
+
101
+ def logger(*args)
102
+ self.configuration.logger *args
103
+ end
104
+
105
+ def verbose_logging(*args)
106
+ self.configuration.verbose_logging *args
107
+ end
108
+
109
+ def view_handler_ns(*args)
110
+ self.configuration.view_handler_ns *args
111
+ end
112
+
113
+ def get(path, handler_class_name)
114
+ self.route(:get, path, handler_class_name)
115
+ end
116
+
117
+ def post(path, handler_class_name)
118
+ self.route(:post, path, handler_class_name)
119
+ end
120
+
121
+ def put(path, handler_class_name)
122
+ self.route(:put, path, handler_class_name)
123
+ end
124
+
125
+ def patch(path, handler_class_name)
126
+ self.route(:patch, path, handler_class_name)
127
+ end
128
+
129
+ def delete(path, handler_class_name)
130
+ self.route(:delete, path, handler_class_name)
131
+ end
132
+
133
+ def route(http_method, path, handler_class_name)
134
+ if self.view_handler_ns && !(handler_class_name =~ /^::/)
135
+ handler_class_name = "#{self.view_handler_ns}::#{handler_class_name}"
136
+ end
137
+ Deas::Route.new(http_method, path, handler_class_name).tap do |route|
138
+ self.configuration.routes.push(route)
139
+ end
140
+ end
141
+
142
+ def self.method_missing(method, *args, &block)
143
+ self.instance.send(method, *args, &block)
144
+ end
145
+
146
+ def self.respond_to?(*args)
147
+ super || self.instance.respond_to?(*args)
148
+ end
149
+
150
+ end
151
+
152
+ end
@@ -0,0 +1,54 @@
1
+ require 'sinatra/base'
2
+
3
+ module Deas
4
+
5
+ module SinatraApp
6
+
7
+ def self.new(server_config)
8
+ server_config.init_proc.call
9
+ server_config.routes.each(&:constantize!)
10
+
11
+ Class.new(Sinatra::Base).tap do |app|
12
+
13
+ # built-in settings
14
+ app.set :environment, server_config.env
15
+ app.set :root, server_config.root
16
+
17
+ app.set :app_file, server_config.app_file
18
+ app.set :public_folder, server_config.public_folder
19
+ app.set :views, server_config.views_folder
20
+
21
+ app.set :dump_errors, server_config.dump_errors
22
+ app.set :logging, false
23
+ app.set :method_override, server_config.method_override
24
+ app.set :sessions, server_config.sessions
25
+ app.set :show_exceptions, server_config.show_exceptions
26
+ app.set :static, server_config.static_files
27
+
28
+ # custom settings
29
+ app.set :logger, server_config.logger
30
+ app.set :runner_logger, server_config.runner_logger
31
+
32
+ # routes
33
+ server_config.routes.each do |route|
34
+ # defines Sinatra routes like:
35
+ # before('/'){ ... }
36
+ # get('/'){ ... }
37
+ # after('/'){ ... }
38
+ app.before(route.path) do
39
+ @runner = route.runner(self).setup
40
+ end
41
+ app.send(route.method, route.path) do
42
+ @runner.run
43
+ end
44
+ app.after(route.path) do
45
+ @runner.teardown
46
+ end
47
+ end
48
+
49
+ end
50
+ end
51
+
52
+ end
53
+
54
+ end
@@ -0,0 +1,119 @@
1
+ require 'deas/runner'
2
+ require 'deas/template'
3
+
4
+ module Deas
5
+
6
+ class SinatraRunner < Runner
7
+
8
+ def initialize(handler_class, sinatra_call)
9
+ @sinatra_call = sinatra_call
10
+ @handler_class = handler_class
11
+ @logger = @sinatra_call.settings.logger
12
+ @runner_logger = @sinatra_call.settings.runner_logger
13
+ @params = @sinatra_call.params
14
+ @request = @sinatra_call.request
15
+ @response = @sinatra_call.response
16
+ @session = @sinatra_call.session
17
+ @time_taken = nil
18
+ @started_at = nil
19
+ super(handler_class)
20
+ end
21
+
22
+ def setup
23
+ @started_at = Time.now
24
+ log_verbose "===== Received request ====="
25
+ log_verbose " Method: #{@request.request_method.inspect}"
26
+ log_verbose " Path: #{@request.path.inspect}"
27
+ log_verbose " Params: #{@sinatra_call.params.inspect}"
28
+ log_verbose " Handler: #{@handler_class}"
29
+ self
30
+ end
31
+
32
+ def run
33
+ run_callbacks @handler_class.before_callbacks
34
+ @handler.init
35
+ response_data = @handler.run
36
+ run_callbacks @handler_class.after_callbacks
37
+ response_data
38
+ end
39
+
40
+ # expects that `setup` has been run; this method is dependent on it
41
+ CODE_NAMES = {
42
+ 200 => 'OK',
43
+ 400 => 'BAD REQUEST' ,
44
+ 401 => 'UNAUTHORIZED',
45
+ 403 => 'FORBIDDEN',
46
+ 404 => 'NOT FOUND',
47
+ 408 => 'TIMEOUT',
48
+ 500 => 'ERROR'
49
+ }
50
+ def teardown
51
+ @time_taken = RoundedTime.new(Time.now - @started_at)
52
+ @response.status.tap do |code|
53
+ response_display = [ code, CODE_NAMES[code.to_i] ].compact.join(', ')
54
+ log_verbose "===== Completed in #{@time_taken}ms " \
55
+ "#{response_display} ====="
56
+ end
57
+ log_summary SummaryLine.new({
58
+ 'status' => @response.status,
59
+ 'method' => @request.request_method,
60
+ 'path' => @request.path,
61
+ 'params' => @params,
62
+ 'time' => @time_taken,
63
+ 'handler' => @handler_class
64
+ })
65
+ self
66
+ end
67
+
68
+ def log_verbose(message, level = :info)
69
+ @runner_logger.verbose.send(level, "[Deas] #{message}")
70
+ end
71
+
72
+ def log_summary(message, level = :info)
73
+ @runner_logger.summary.send(level, "[Deas] #{message}")
74
+ end
75
+
76
+ # Helpers
77
+
78
+ def halt(*args)
79
+ @sinatra_call.halt(*args)
80
+ end
81
+
82
+ def render(name, options = nil, &block)
83
+ options ||= {}
84
+ options[:locals] = { :view => @handler }.merge(options[:locals] || {})
85
+ options[:layout] ||= @handler_class.layouts
86
+ Deas::Template.new(@sinatra_call, name, options).render(&block)
87
+ end
88
+
89
+ def redirect(*args)
90
+ @sinatra_call.redirect(*args)
91
+ end
92
+
93
+ def redirect_to(path, *args)
94
+ @sinatra_call.redirect(@sinatra_call.to(path), *args)
95
+ end
96
+
97
+ private
98
+
99
+ def run_callbacks(callbacks)
100
+ callbacks.each{|proc| @handler.instance_eval(&proc) }
101
+ end
102
+
103
+ module RoundedTime
104
+ ROUND_PRECISION = 2
105
+ ROUND_MODIFIER = 10 ** ROUND_PRECISION
106
+ def self.new(time_in_seconds)
107
+ (time_in_seconds * 1000 * ROUND_MODIFIER).to_i / ROUND_MODIFIER.to_f
108
+ end
109
+ end
110
+
111
+ module SummaryLine
112
+ def self.new(line_attrs)
113
+ attr_keys = %w{time status handler method path params}
114
+ attr_keys.map{ |k| "#{k}=#{line_attrs[k].inspect}" }.join(' ')
115
+ end
116
+ end
117
+
118
+ end
119
+ end
@@ -0,0 +1,80 @@
1
+ require 'rack'
2
+
3
+ module Deas
4
+
5
+ class Template
6
+
7
+ def self.helpers(*helper_modules)
8
+ Deas::Template::RenderScope.class_eval{ include *helper_modules }
9
+ end
10
+
11
+ attr_reader :name, :options
12
+
13
+ def initialize(sinatra_call, name, options = nil)
14
+ @options = options || {}
15
+ @options[:scope] = RenderScope.new(sinatra_call)
16
+
17
+ @sinatra_call = sinatra_call
18
+ @name = name.to_sym
19
+ (@options.delete(:layout) || @options.delete(:layouts) || []).tap do |l|
20
+ @layouts = l.compact.map(&:to_sym)
21
+ end
22
+ end
23
+
24
+ # builds Sinatra render-blocks like:
25
+ #
26
+ # erb :main_layout do
27
+ # erb :second_layout do
28
+ # erb :user_index
29
+ # end
30
+ # end
31
+ #
32
+ def render(&block)
33
+ template_names = [ @layouts, @name ].flatten.reverse
34
+ top_render_proc = template_names.inject(block) do |render_proc, name|
35
+ proc{ @sinatra_call.erb(name, @options, &render_proc) }
36
+ end
37
+ top_render_proc.call
38
+ end
39
+
40
+ class RenderScope
41
+ def initialize(sinatra_call)
42
+ @sinatra_call = sinatra_call
43
+ end
44
+
45
+ def render(name, options = nil, &block)
46
+ Deas::Template.new(@sinatra_call, name, options || {}).render(&block)
47
+ end
48
+
49
+ def partial(name, locals = nil)
50
+ Deas::Partial.new(@sinatra_call, name, locals || {}).render
51
+ end
52
+
53
+ def escape_html(html)
54
+ Rack::Utils.escape_html(html)
55
+ end
56
+ alias :h :escape_html
57
+
58
+ def escape_url(path)
59
+ Rack::Utils.escape_path(path)
60
+ end
61
+ alias :u :escape_url
62
+
63
+ end
64
+
65
+ end
66
+
67
+ class Partial < Template
68
+
69
+ def initialize(sinatra_call, name, locals = nil)
70
+ options = { :locals => (locals || {}) }
71
+ name = begin
72
+ basename = File.basename(name.to_s)
73
+ name.to_s.sub(/#{basename}\Z/, "_#{basename}")
74
+ end
75
+ super sinatra_call, name, options
76
+ end
77
+
78
+ end
79
+
80
+ end
@@ -0,0 +1,15 @@
1
+ require 'deas/test_runner'
2
+
3
+ module Deas
4
+
5
+ module TestHelpers
6
+
7
+ module_function
8
+
9
+ def test_runner(handler_class, args = nil)
10
+ TestRunner.new(handler_class, args)
11
+ end
12
+
13
+ end
14
+
15
+ end
@@ -0,0 +1,40 @@
1
+ require 'deas/logger'
2
+ require 'deas/runner'
3
+
4
+ module Deas
5
+
6
+ class TestRunner < Runner
7
+
8
+ attr_reader :handler, :return_value
9
+
10
+ def initialize(handler_class, args = nil)
11
+ args = (args || {}).dup
12
+ @logger = args.delete(:logger) || Deas::NullLogger.new
13
+ @params = args.delete(:params) || {}
14
+ @request = args.delete(:request)
15
+ @response = args.delete(:response)
16
+ @session = args.delete(:session)
17
+
18
+ super(handler_class)
19
+ args.each{|key, value| @handler.send("#{key}=", value) }
20
+
21
+ @return_value = catch(:halt){ @handler.init; nil }
22
+ end
23
+
24
+ def run
25
+ @return_value ||= catch(:halt){ @handler.run }
26
+ end
27
+
28
+ # Helpers
29
+
30
+ def halt(*args)
31
+ throw(:halt, args)
32
+ end
33
+
34
+ def render(*args)
35
+ "test runner render"
36
+ end
37
+
38
+ end
39
+
40
+ end
data/lib/deas/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Deas
2
- VERSION = "0.0.2"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -0,0 +1,97 @@
1
+ require 'deas/runner'
2
+
3
+ module Deas
4
+
5
+ module ViewHandler
6
+
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ extend ClassMethods
10
+ end
11
+ end
12
+
13
+ def initialize(runner)
14
+ @deas_runner = runner
15
+ end
16
+
17
+ def init
18
+ self.run_callback 'before_init'
19
+ self.init!
20
+ self.run_callback 'after_init'
21
+ end
22
+
23
+ def init!
24
+ end
25
+
26
+ def run
27
+ self.run_callback 'before_run'
28
+ data = self.run!
29
+ self.run_callback 'after_run'
30
+ data
31
+ end
32
+
33
+ def run!
34
+ raise NotImplementedError
35
+ end
36
+
37
+ def inspect
38
+ reference = '0x0%x' % (self.object_id << 1)
39
+ "#<#{self.class}:#{reference} @request=#{self.request.inspect}>"
40
+ end
41
+
42
+ protected
43
+
44
+ def before_init; end
45
+ def after_init; end
46
+ def before_run; end
47
+ def after_run; end
48
+
49
+ # Helpers
50
+
51
+ def halt(*args); @deas_runner.halt(*args); end
52
+ def redirect(*args); @deas_runner.redirect(*args); end
53
+ def redirect_to(*args); @deas_runner.redirect_to(*args); end
54
+
55
+ def render(*args, &block)
56
+ @deas_runner.render(*args, &block)
57
+ end
58
+
59
+ def logger; @deas_runner.logger; end
60
+ def request; @deas_runner.request; end
61
+ def response; @deas_runner.response; end
62
+ def params; @deas_runner.params; end
63
+ def session; @deas_runner.session; end
64
+
65
+ def run_callback(callback)
66
+ self.send(callback.to_s)
67
+ end
68
+
69
+ module ClassMethods
70
+
71
+ def before(&block)
72
+ self.before_callbacks << block
73
+ end
74
+
75
+ def before_callbacks
76
+ @before_callbacks ||= []
77
+ end
78
+
79
+ def after(&block)
80
+ self.after_callbacks << block
81
+ end
82
+
83
+ def after_callbacks
84
+ @after_callbacks ||= []
85
+ end
86
+
87
+ def layout(*args)
88
+ @layouts = args unless args.empty?
89
+ @layouts
90
+ end
91
+ alias :layouts :layout
92
+
93
+ end
94
+
95
+ end
96
+
97
+ end