expressr 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/expressr.rb CHANGED
@@ -1,4 +1,10 @@
1
- require "#{directory}/expressr/version.rb"
1
+ require 'mime-types'
2
+ require 'hashie'
3
+ require 'noder'
4
+
5
+ directory = File.dirname(File.absolute_path(__FILE__))
6
+ Dir.glob("#{directory}/expressr/*.rb") { |file| require file }
7
+ Dir.glob("#{directory}/expressr/listeners/*.rb") { |file| require file }
2
8
 
3
9
  module Expressr
4
10
  end
@@ -0,0 +1,108 @@
1
+ require 'pathname'
2
+
3
+ module Expressr
4
+ class App
5
+ extend Forwardable
6
+
7
+ attr_reader :router, :server
8
+
9
+ def_delegators :@router, :put, :post, :delete, :use, :route
10
+
11
+ DEFAULTS = {
12
+ 'jsonp callback name' => 'callback',
13
+ 'locals' => {},
14
+ 'root' => nil,
15
+ 'view engine' => 'slim',
16
+ 'views' => 'views'
17
+ }
18
+
19
+ class << self
20
+ def settings
21
+ @settings ||= DEFAULTS
22
+ end
23
+ end
24
+
25
+ def initialize(server_options={})
26
+ @router = Router.new
27
+ @locals = {}
28
+ set_default_root
29
+ @server_options = { app: self }.merge(server_options)
30
+ @server = Noder::HTTP::Server.new(@server_options)
31
+ @router.server = @server
32
+ request_stack = @server.event_stack('request')
33
+ request_stack.replace(Noder::HTTP::Listeners::Request, callback: Expressr::Listeners::Request)
34
+ request_stack.replace(Noder::HTTP::Listeners::Response, callback: Expressr::Listeners::Response)
35
+ end
36
+
37
+ def set(name, value)
38
+ settings[name] = value
39
+ end
40
+
41
+ def get(name_or_path, &block)
42
+ if block
43
+ @router.get(name_or_path, &block)
44
+ else
45
+ settings[name_or_path]
46
+ end
47
+ end
48
+
49
+ def enable(name)
50
+ settings[name] = true
51
+ end
52
+
53
+ def disable(name)
54
+ settings[name] = false
55
+ end
56
+
57
+ def enabled?(name)
58
+ settings[name] == true
59
+ end
60
+
61
+ def disabled?(name)
62
+ settings[name] == false
63
+ end
64
+
65
+ def engine(value)
66
+ set('view engine', value)
67
+ end
68
+
69
+ def param(name, &block)
70
+ @router.add_route(block, param: name)
71
+ end
72
+
73
+ def all(path, &block)
74
+ @router.add_route(block, path: path)
75
+ end
76
+
77
+ def locals
78
+ settings['locals']
79
+ end
80
+
81
+ def render(view, options={}, &block)
82
+ raise NotImplementedError
83
+ end
84
+
85
+ def listen(port=nil, address=nil, &block)
86
+ @server_options[:port] = port if port
87
+ @server_options[:address] = address if address
88
+ @server.listen(@server_options[:port], @server_options[:address], {}, &block)
89
+ end
90
+
91
+ def close
92
+ @server.close
93
+ end
94
+
95
+ def settings
96
+ self.class.settings
97
+ end
98
+
99
+ private
100
+
101
+ def set_default_root
102
+ return if get('root')
103
+ caller_path = caller[1].split(':')[0]
104
+ caller_directory = File.dirname(caller_path)
105
+ set('root', Pathname.new(caller_directory))
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,15 @@
1
+ require 'json'
2
+
3
+ module Expressr
4
+ class JSON
5
+ class << self
6
+ def decode(string)
7
+ ::JSON.load(string)
8
+ end
9
+
10
+ def encode(value)
11
+ ::JSON.dump(value)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Expressr
2
+ module Listeners
3
+ class Request < Noder::Events::Listeners::Base
4
+ def call(env)
5
+ env[:request] = Expressr::Request.new(env[:request_env])
6
+ callback.call(env) if callback
7
+ env
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module Expressr
2
+ module Listeners
3
+ class Response < Noder::Events::Listeners::Base
4
+ def call(env)
5
+ env[:response] = Expressr::Response.new(env)
6
+ callback.call(env) if callback
7
+ env
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,39 @@
1
+ module Expressr
2
+ class Renderer
3
+ class << self
4
+ attr_accessor :engine
5
+
6
+ def renderer
7
+ if engine != Expressr::App.settings['view engine']
8
+ @renderer = get_renderer
9
+ else
10
+ @renderer ||= get_renderer
11
+ end
12
+ end
13
+
14
+ def get_renderer
15
+ self.engine = engines[Expressr::App.settings['view engine']]
16
+ raise "Invalid view engine value: #{engine}" unless engine
17
+ klass = Expressr::Utils.constantize(engine)
18
+ klass.new
19
+ end
20
+
21
+ def engines
22
+ {
23
+ 'haml' => 'Expressr::Renderers::Haml',
24
+ 'slim' => 'Expressr::Renderers::Slim'
25
+ }
26
+ end
27
+ end
28
+
29
+ def render(path, locals={})
30
+ path = App.settings['root'].join(App.settings['views'], path)
31
+ locals = App.settings['locals'].merge(locals)
32
+ renderer.render(path, locals)
33
+ end
34
+
35
+ def renderer
36
+ self.class.renderer
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ module Expressr
2
+ module Renderers
3
+ directory = "#{File.dirname(File.absolute_path(__FILE__))}/renderers"
4
+
5
+ autoload :Base, "#{directory}/base"
6
+ autoload :Haml, "#{directory}/haml"
7
+ autoload :Slim, "#{directory}/slim"
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module Expressr
2
+ module Renderers
3
+ class Base
4
+ def add_extension(path, extension)
5
+ return "#{path}.#{extension}" if path.extname == ''
6
+ path
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,13 @@
1
+ module Expressr
2
+ module Renderers
3
+ class Haml < Base
4
+ require 'haml'
5
+
6
+ def render(view, locals={})
7
+ view = add_extension(view, 'haml')
8
+ content = File.read(view)
9
+ ::Haml::Engine.new(content).render(nil, locals)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,12 @@
1
+ module Expressr
2
+ module Renderers
3
+ class Slim < Base
4
+ require 'slim'
5
+
6
+ def render(view, locals={})
7
+ view = add_extension(view, 'slim')
8
+ ::Slim::Template.new(view).render(nil, locals)
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,118 @@
1
+ module Expressr
2
+ class Request < Noder::HTTP::Request
3
+ attr_reader :query
4
+
5
+ def initialize(env)
6
+ super(env)
7
+ @params = Hashie::Mash.new(@params)
8
+ @query = Hashie::Mash.new(@query)
9
+ end
10
+
11
+ def url
12
+ @url ||= original_url
13
+ end
14
+
15
+ def param(name)
16
+ params[name]
17
+ end
18
+
19
+ def route
20
+ raise NotImplementedError
21
+ end
22
+
23
+ def cookies
24
+ raise NotImplementedError
25
+ end
26
+
27
+ def signed_cookies
28
+ raise NotImplementedError
29
+ end
30
+
31
+ def get(name)
32
+ headers[name]
33
+ end
34
+
35
+ def accepts(types)
36
+ types = types.split(', ') if types.is_a?(String)
37
+ types = [types] unless types.is_a?(Array)
38
+ @accept_types ||= get('Accept').split(';').first.split(',')
39
+ (types & @accept_types).first
40
+ end
41
+
42
+ def accepts_charset(charset)
43
+ raise NotImplementedError
44
+ end
45
+
46
+ def accepts_language(language)
47
+ raise NotImplementedError
48
+ end
49
+
50
+ def is?(type)
51
+ content_type_header = headers['Content-Type']
52
+ return false if content_type_header.nil?
53
+ content_types = content_type_header.split('; ')
54
+ content_types.include?(type)
55
+ end
56
+
57
+ def ip
58
+ @env[:ip]
59
+ end
60
+
61
+ def ips
62
+ raise NotImplementedError
63
+ end
64
+
65
+ def path
66
+ @env[:request_uri]
67
+ end
68
+
69
+ def host
70
+ get('Host').split(':').first
71
+ end
72
+
73
+ def fresh?
74
+ raise NotImplementedError
75
+ end
76
+
77
+ def stale?
78
+ raise NotImplementedError
79
+ end
80
+
81
+ def xhr?
82
+ get('X-Requested-With') == 'XMLHttpRequest'
83
+ end
84
+
85
+ def protocol
86
+ @env[:protocol].split('/').first.downcase
87
+ end
88
+
89
+ def secure?
90
+ protocol == 'https'
91
+ end
92
+
93
+ def subdomains
94
+ host.split('.')[0..-3].reverse
95
+ end
96
+
97
+ def locals
98
+ @locals ||= Hashie::Mash.new
99
+ end
100
+
101
+ def original_url
102
+ @original_url ||= get_original_url
103
+ end
104
+
105
+ protected
106
+
107
+ def get_original_url
108
+ url = path
109
+ query_string = @env[:query_string]
110
+ if query_string && query_string != ''
111
+ url = "#{url}?#{query_string}"
112
+ end
113
+ url
114
+ end
115
+
116
+ alias_method :header, :get
117
+ end
118
+ end
@@ -0,0 +1,188 @@
1
+ require 'cgi'
2
+
3
+ module Expressr
4
+ class Response < Noder::HTTP::Response
5
+ DEFAULT_STATUS = 200
6
+
7
+ attr_accessor :cookies, :locals, :status
8
+ attr_reader :request
9
+
10
+ def initialize(env)
11
+ super(env)
12
+ @request = env[:request]
13
+ @cookies = {}
14
+ end
15
+
16
+ # Defining #status(status) conflicts with EventMachine::DelegatedHttpResponse's #status
17
+
18
+ def set(key_or_hash, value=nil)
19
+ if key_or_hash.is_a?(Hash)
20
+ hash = key_or_hash
21
+ hash.each do |key, value|
22
+ set_header(key, value)
23
+ end
24
+ else
25
+ key = key_or_hash
26
+ set_header(key, value)
27
+ end
28
+ end
29
+
30
+ def get(key)
31
+ get_header(key)
32
+ end
33
+
34
+ def cookie(name, value, options={})
35
+ options = { 'name' => name, 'value' => value }.merge(options)
36
+ @cookies[name] = CGI::Cookie::new(options)
37
+ set_cookies
38
+ end
39
+
40
+ def clear_cookie(name, options={})
41
+ cookie(name, '', options)
42
+ end
43
+
44
+ def redirect(status_or_url, url=nil)
45
+ if url
46
+ status = status_or_url
47
+ else
48
+ status = 302
49
+ url = status_or_url
50
+ end
51
+ write_head(status)
52
+ location(url)
53
+ self.end
54
+ end
55
+
56
+ def location(url)
57
+ set_header('Location', url)
58
+ end
59
+
60
+ # Equivalent of Express.js's send
61
+ def out(status_or_content=nil, content=nil)
62
+ if status_or_content.is_a?(Integer)
63
+ status = status_or_content
64
+ else
65
+ status = DEFAULT_STATUS
66
+ content = status_or_content || ''
67
+ end
68
+ if content.is_a?(Hash) || content.is_a?(Array)
69
+ json(status, content)
70
+ else
71
+ type('text/html') unless get('Content-Type')
72
+ write_head(status)
73
+ write(content)
74
+ self.end
75
+ end
76
+ end
77
+
78
+ def json(status_or_body, body=nil)
79
+ if status_or_body.is_a?(Integer)
80
+ status = status_or_body
81
+ else
82
+ status = DEFAULT_STATUS
83
+ body = status_or_body
84
+ end
85
+ type('application/json')
86
+ write_head(status)
87
+ write(Expressr::JSON.encode(body))
88
+ self.end
89
+ end
90
+
91
+ def jsonp(status_or_body, body=nil)
92
+ callback_name = Expressr::App.settings['jsonp callback name']
93
+ callback = params[callback_name]
94
+ if callback.nil?
95
+ json(status_or_body, body)
96
+ return
97
+ end
98
+ if status_or_body.is_a?(Integer)
99
+ status = status_or_body
100
+ else
101
+ status = DEFAULT_STATUS
102
+ body = status_or_body
103
+ end
104
+ type('application/javascript')
105
+ write_head(status)
106
+ body = Expressr::JSON.encode(body)
107
+ body = "#{callback}(#{body});"
108
+ write(body)
109
+ self.end
110
+ end
111
+
112
+ def type(value)
113
+ set_header('Content-Type', value)
114
+ end
115
+
116
+ def format(hash)
117
+ hash.each do |content_type, proc|
118
+ content_type = Utils.standardize_content_type(content_type)
119
+ if request.accepts(content_type)
120
+ proc.call(@request, self)
121
+ end
122
+ end
123
+ end
124
+
125
+ def attachment(filename=nil)
126
+ content_disposition = 'attachment'
127
+ if filename
128
+ basename = File.basename(filename)
129
+ content_disposition += %Q|; filename="#{basename}"|
130
+ content_type = MIME::Types.type_for(filename).first.to_s
131
+ type(content_type)
132
+ end
133
+ set_header('Content-Disposition', content_disposition)
134
+ end
135
+
136
+ def send_file(path, options={})
137
+ root = nil
138
+ if options[:root] && !path.start_with?('/')
139
+ root = Pathname.new(options[:root])
140
+ elsif App.settings['root']
141
+ root = App.settings['root']
142
+ end
143
+ path = root.join(path).to_s if root
144
+ attachment(path) unless options[:bypass_headers]
145
+ write(read_file(path))
146
+ self.end
147
+ end
148
+
149
+ def download(path, filename=nil)
150
+ attachment(filename || path)
151
+ send_file(path, bypass_headers: true)
152
+ end
153
+
154
+ def links(links)
155
+ value = links.map { |key, url| %Q|<#{url}>; rel="#{key}"| }.join(', ')
156
+ set_header('Link', value)
157
+ end
158
+
159
+ def locals
160
+ @locals ||= {}
161
+ end
162
+
163
+ def render(view, locals=nil, &block)
164
+ body = nil
165
+ begin
166
+ body = Renderer.new.render(view, locals)
167
+ rescue Exception => e
168
+ if block
169
+ block.call(e)
170
+ else
171
+ raise
172
+ end
173
+ end
174
+ out(body)
175
+ end
176
+
177
+ protected
178
+
179
+ def read_file(path)
180
+ File.open(path, 'rb').read
181
+ end
182
+
183
+ def set_cookies
184
+ cookie_string = @cookies.values.map(&:to_s).join("\nSet-Cookie: ")
185
+ set_header('Set-Cookie', cookie_string)
186
+ end
187
+ end
188
+ end