expressr 0.0.2 → 0.0.3

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/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