deas 0.0.2 → 0.1.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/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