halcyon 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,100 @@
1
+ require 'erb'
2
+
3
+ module Halcyon
4
+ class Config
5
+
6
+ # Class to assist with loading configuration from a file.
7
+ #
8
+ # Examples:
9
+ #
10
+ # Halcyon::Config::File.new(file_name_or_path).to_hash #=> {...}
11
+ #
12
+ class File
13
+
14
+ attr_accessor :path
15
+ attr_accessor :content
16
+
17
+ # Creates a profile with the default paths.
18
+ #
19
+ # * +file+ is the path to the file.
20
+ # * +filter_config+ specifies whether to filter the contents through ERB
21
+ # before parsing it.
22
+ #
23
+ def initialize(file, filter_config = true)
24
+ if ::File.exist?(file)
25
+ self.path = file
26
+ elsif ::File.exist?(Halcyon.paths.for(:config)/file)
27
+ self.path = Halcyon.paths.for(:config)/file
28
+ else
29
+ raise ArgumentError.new("Could not find #{self.path} (it does not exist).")
30
+ end
31
+ self.content = self.filter(::File.read(self.path), filter_config)
32
+ end
33
+
34
+ # Returns the loaded configuration file's contents parsed by the
35
+ # marshal format loader (defaulting to YAML, also providing JSON).
36
+ #
37
+ # Examples:
38
+ #
39
+ # p = Halcyon.paths.for(:config)/'config.yml'
40
+ # c = Halcyon::Config::File.new(p)
41
+ # c.to_hash #=> the contents of the config file parsed as YAML
42
+ # c.to_hash(:from_json) #=> same as above only parsed as JSON
43
+ # # parsing errors will happen if you try to use the wrong marshal
44
+ # # load method
45
+ #
46
+ def to_hash(from = :from_yaml)
47
+ case from
48
+ when :from_yaml
49
+ require 'yaml'
50
+ YAML.load(self.content)
51
+ when :from_json
52
+ JSON.parse(self.content)
53
+ end
54
+ end
55
+
56
+ # Filters the contents through ERB.
57
+ #
58
+ def filter(content, filter_through_erb)
59
+ content = ERB.new(content).result if filter_through_erb
60
+ content
61
+ end
62
+
63
+ def inspect
64
+ "#<Halcyon::Config::File #{self.path}>"
65
+ end
66
+
67
+ class << self
68
+
69
+ # Provides a convenient way to load the configuration and return the
70
+ # appropriate hash contents.
71
+ #
72
+ def load(path)
73
+ file = File.new(path)
74
+ case path
75
+ when /\.(yaml|yml)/
76
+ file.to_hash
77
+ when /\.(json)/
78
+ file.to_hash(:from_json)
79
+ end
80
+ end
81
+
82
+ # Loads the configuration file and parses it's contents as YAML.
83
+ # This is a shortcut method.
84
+ #
85
+ def load_from_yaml(path)
86
+ self.new(path).to_hash
87
+ end
88
+
89
+ # Loads the configuration file and parses it's contents as JSON.
90
+ # This is a shortcut method.
91
+ #
92
+ def load_from_json(path)
93
+ self.new(path).to_hash(:from_json)
94
+ end
95
+
96
+ end
97
+
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,180 @@
1
+ module Halcyon
2
+ class Config
3
+ module Helpers
4
+
5
+ # Extends the target class with the Configurable and Accessors helpers.
6
+ def self.included(target)
7
+ target.extend(Configurable)
8
+ target.extend(Accessors)
9
+ end
10
+
11
+ # Provides several convenience accessors for configuration values,
12
+ # including these:
13
+ # * <tt>app</tt>: the app name
14
+ # * <tt>root</tt>: the application working directory
15
+ # * <tt>db</tt>: database configuration settings
16
+ #
17
+ module Accessors
18
+
19
+ # Accesses the <tt>app</tt> config value which is the constantized
20
+ # version of the application name (which can be set manually in the
21
+ # config file as <tt>app: NameOfApp</tt>, defaulting to a camel case
22
+ # version of the application directory name).
23
+ #
24
+ def app
25
+ self.config[:app] || ::File.dirname(self.root).camel_case
26
+ end
27
+
28
+ # Sets the application name.
29
+ #
30
+ def app=(name)
31
+ self.config[:app] = name
32
+ end
33
+
34
+ # Accesses the <tt>root</tt> config value which is the root of the
35
+ # current Halcyon application (usually <tt>Dir.pwd</tt>).
36
+ #
37
+ # Defaults to <tt>Dir.pwd</tt>
38
+ #
39
+ def root
40
+ self.config[:root] || Dir.pwd rescue Dir.pwd
41
+ end
42
+
43
+ # Sets the application root.
44
+ #
45
+ def root=(path)
46
+ self.config[:root] = path
47
+ end
48
+
49
+ # Accesses the <tt>db</tt> config value. Intended to contain the
50
+ # database configuration values for whichever ORM is used.
51
+ #
52
+ def db
53
+ self.config[:db]
54
+ end
55
+
56
+ # Sets the database settings.
57
+ #
58
+ def db=(config)
59
+ self.config[:db] = config
60
+ end
61
+
62
+ # Accesses the <tt>environment</tt> config value. Intended to contain
63
+ # the environment the application is running in.
64
+ #
65
+ # Defaults to the <tt>development</tt> environment.
66
+ #
67
+ def environment
68
+ self.config[:environment] || :development
69
+ end
70
+ alias_method :env, :environment
71
+
72
+ # Sets the environment config value.
73
+ #
74
+ def environment=(env)
75
+ self.config[:environment] = env.to_sym
76
+ end
77
+ alias_method :env=, :environment=
78
+
79
+ # Provides a proxy to the Halcyon::Config::Paths instance.
80
+ #
81
+ def paths
82
+ self.config[:paths]
83
+ end
84
+
85
+ # Provides a proxy to the hooks hash.
86
+ #
87
+ def hooks
88
+ self.config[:hooks]
89
+ end
90
+
91
+ end
92
+
93
+ # Provides dynamic creation of configuration attribute accessors.
94
+ #
95
+ module Configurable
96
+
97
+ # Defines a dynamic accessor for configuration attributes.
98
+ #
99
+ # Examples:
100
+ #
101
+ # Halcyon.configurable(:db)
102
+ # Halcyon.db = {...}
103
+ # Halcyon.config[:db] #=> {...}
104
+ # Halcyon.config[:db] = true
105
+ # Halcyon.db #=> true
106
+ #
107
+ def configurable(attribute)
108
+ eval <<-"end;"
109
+ def #{attribute.to_s}
110
+ Halcyon.config[:#{attribute.to_s}]
111
+ end
112
+ def #{attribute.to_s}=(value)
113
+ value = value.to_mash if value.is_a?(Hash)
114
+ Halcyon.config[:#{attribute.to_s}] = value
115
+ end
116
+ end;
117
+ end
118
+ alias_method :configurable_attr, :configurable
119
+
120
+ # Defines a dynamic reader for configuration attributes, accepting
121
+ # either a string or a block to perform the action.
122
+ #
123
+ # Examples:
124
+ #
125
+ # Halcyon.configurable_reader(:foo) do
126
+ # self.config[:foo].to_sym
127
+ # end
128
+ #
129
+ # OR
130
+ #
131
+ # Halcyon.configurable_reader(:foo, "Halcyon.config[%s].to_sym")
132
+ #
133
+ def configurable_reader(attribute, code=nil, &block)
134
+ if block_given? and not code
135
+ Halcyon.class.send(:define_method, attribute.to_sym, block)
136
+ elsif code and not block_given?
137
+ Halcyon.class.send(:eval, <<-"end;")
138
+ def #{attribute.to_s}
139
+ #{code % [attribute.to_sym.inspect]}
140
+ end
141
+ end;
142
+ else
143
+ raise ArgumentError.new("Either a block or a code string should be supplied.")
144
+ end
145
+ end
146
+ alias_method :configurable_attr_reader, :configurable_reader
147
+
148
+ # Defines a dynamic writer for configuration attributes, accepting
149
+ # either a string or a block to perform the action.
150
+ #
151
+ # Examples:
152
+ #
153
+ # Halcyon.configurable_writer(:foo) do |val|
154
+ # self.config[:foo] = val.to_sym
155
+ # end
156
+ #
157
+ # OR
158
+ #
159
+ # Halcyon.configurable_reader(:foo, "Halcyon.config[%s] = value.to_sym")
160
+ #
161
+ def configurable_writer(attribute, code=nil, &block)
162
+ if block_given? and not code
163
+ Halcyon.class.send(:define_method, :"#{attribute}=", block)
164
+ elsif code and not block_given?
165
+ Halcyon.class.send(:eval, <<-"end;")
166
+ def #{attribute.to_s}=(value)
167
+ #{code % [attribute.to_sym.inspect]}
168
+ end
169
+ end;
170
+ else
171
+ raise ArgumentError.new("Either a block or a code string should be supplied.")
172
+ end
173
+ end
174
+ alias_method :configurable_attr_writer, :configurable_writer
175
+
176
+ end
177
+
178
+ end
179
+ end
180
+ end
@@ -0,0 +1,73 @@
1
+ module Halcyon
2
+ class Config
3
+ class Paths
4
+
5
+ attr_accessor :paths
6
+
7
+ # Creates a profile with the default paths.
8
+ def initialize(paths={})
9
+ self.paths = Mash.new(self.defaults.merge(paths))
10
+ end
11
+
12
+ # Gets the path for the specified entity.
13
+ #
14
+ # Examples:
15
+ #
16
+ # Halcyon.paths.for(:log) #=> "/path/to/app/log/"
17
+ #
18
+ def for(key)
19
+ self.paths[key] or raise ArgumentError.new("Path is not defined")
20
+ end
21
+
22
+ # Alias to <tt>for</tt>.
23
+ #
24
+ def [](key)
25
+ self.for(key)
26
+ end
27
+
28
+ # Defines a path for the specified entity.
29
+ #
30
+ # Examples:
31
+ #
32
+ # Halcyon.paths.define(:tmp, Halcyon.root/'tmp')
33
+ #
34
+ # OR
35
+ #
36
+ # Halcyon.paths.define(:tmp => Halcyon.root/'tmp')
37
+ #
38
+ def define(key_or_hash, value = nil)
39
+ if key_or_hash.is_a?(Hash) and value.nil?
40
+ key_or_hash.keys.each do |key|
41
+ self.define(key, key_or_hash[key])
42
+ end
43
+ else
44
+ self.paths[key_or_hash] = value
45
+ end
46
+ end
47
+
48
+ # Alias for <tt>define</tt>.
49
+ #
50
+ def []=(key, value)
51
+ self.define(key, value)
52
+ end
53
+
54
+ # Default paths.
55
+ #
56
+ def defaults
57
+ {
58
+ :controller => Halcyon.root/'app',
59
+ :model => Halcyon.root/'app'/'models',
60
+ :lib => Halcyon.root/'lib',
61
+ :config => Halcyon.root/'config',
62
+ :init => Halcyon.root/'config'/'init',
63
+ :log => Halcyon.root/'log'
64
+ }
65
+ end
66
+
67
+ def inspect
68
+ "#<Halcyon::Config::Paths #{self.paths.keys.join(', ')}>"
69
+ end
70
+
71
+ end
72
+ end
73
+ end
@@ -12,28 +12,27 @@ module Halcyon
12
12
  # Sets the <tt>@env</tt> and <tt>@request</tt> instance variables, used by
13
13
  # various helping methods.
14
14
  # +env+ the request environment details
15
+ #
15
16
  def initialize(env)
16
17
  @env = env
17
18
  @request = Rack::Request.new(@env)
18
19
  end
19
20
 
20
- class << self
21
-
22
- # Not implemented.
23
- def before method, &proc
24
- raise NotImplemented.new
25
- end
26
-
27
- # Not implemented.
28
- def after method, &proc
29
- raise NotImplemented.new
30
- end
31
-
21
+ # Used internally.
22
+ #
23
+ # Dispatches the action specified, including all filters.
24
+ #
25
+ def _dispatch(action)
26
+ _run_filters(:before, action)
27
+ response = send(action)
28
+ _run_filters(:after, action)
29
+ response
32
30
  end
33
31
 
34
32
  # Returns the request params and the route params.
35
33
  #
36
34
  # Returns Hash:{route_params, get_params, post_params}.to_mash
35
+ #
37
36
  def params
38
37
  self.request.params.merge(self.env['halcyon.route']).to_mash
39
38
  end
@@ -41,6 +40,7 @@ module Halcyon
41
40
  # Returns any POST params, excluding GET and route params.
42
41
  #
43
42
  # Returns Hash:{...}.to_mash
43
+ #
44
44
  def post
45
45
  self.request.POST.to_mash
46
46
  end
@@ -48,6 +48,7 @@ module Halcyon
48
48
  # Returns any GET params, excluding POST and route params.
49
49
  #
50
50
  # Returns Hash:{...}.to_mash
51
+ #
51
52
  def get
52
53
  self.request.GET.to_mash
53
54
  end
@@ -55,6 +56,7 @@ module Halcyon
55
56
  # Returns query params (usually the GET params).
56
57
  #
57
58
  # Returns Hash:{...}.to_mash
59
+ #
58
60
  def query_params
59
61
  Rack::Utils.parse_query(self.env['QUERY_STRING']).to_mash
60
62
  end
@@ -62,6 +64,7 @@ module Halcyon
62
64
  # The path of the request URL.
63
65
  #
64
66
  # Returns String:/path/of/request
67
+ #
65
68
  def uri
66
69
  # special parsing is done to remove the protocol, host, and port that
67
70
  # some Handlers leave in there. (Fixes inconsistencies.)
@@ -71,10 +74,77 @@ module Halcyon
71
74
  # The request method.
72
75
  #
73
76
  # Returns Symbol:get|post|put|delete
77
+ #
74
78
  def method
75
79
  self.env['REQUEST_METHOD'].downcase.to_sym
76
80
  end
77
81
 
82
+ # Returns the name of the controller in path form.
83
+ #
84
+ def self.controller_name
85
+ @controller_name ||= self.name.to_const_path
86
+ end
87
+
88
+ # Returns the name of the controller in path form.
89
+ #
90
+ def controller_name
91
+ self.class.controller_name
92
+ end
93
+
94
+ # Generates a URL based on the given name and passed
95
+ # options. Used with named routes and resources:
96
+ #
97
+ # url(:users) # => "/users"
98
+ # url(:admin_permissons) # => "/admin/permissions"
99
+ # url(:user, @user) # => "/users/1"
100
+ #
101
+ # Based on the identical method of Merb's controller.
102
+ #
103
+ def url(name, rparams={})
104
+ Halcyon::Application::Router.generate(name, rparams,
105
+ { :controller => controller_name,
106
+ :action => method
107
+ }
108
+ )
109
+ end
110
+
111
+ #--
112
+ # Responders
113
+ #++
114
+
115
+ # Responds with the correct status and body specified.
116
+ #
117
+ # * +state+ a snake-case symbol of the status (ie, <tt>:not_found</tt>,
118
+ # <tt>:unprocessable_entity</tt>, or <tt>:forbidden</tt>)
119
+ # * +body+ the response body (if the default is insufficient)
120
+ #
121
+ # Examples:
122
+ #
123
+ # class Foos < Application
124
+ # def show
125
+ # if (foo = Foo[params[:id]])
126
+ # ok(foo)
127
+ # else
128
+ # status :not_found
129
+ # end
130
+ # end
131
+ # end
132
+ #
133
+ # The above example is the same as <tt>raise NotFound.new</tt>.
134
+ #
135
+ # If the state specified is not found, it will
136
+ # <tt>raise ServiceUnavailable.new</tt> (a <tt>503</tt> error). The error
137
+ # is then logged along with the backtrace.
138
+ #
139
+ def status(state, body = nil)
140
+ raise Halcyon::Exceptions.const_get(state.to_s.camel_case.to_sym).new(body)
141
+ rescue NameError => e
142
+ self.logger.error "Invalid status #{state.inspect} specified."
143
+ self.logger.error "Backtrace:\n" << e.backtrace.join("\n\t")
144
+ raise Halcyon::Exceptions::ServiceUnavailable.new
145
+ end
146
+ alias_method :error, :status
147
+
78
148
  # Formats message into the standard success response hash, with a status of
79
149
  # 200 (the standard success response).
80
150
  # +body+ the body of the response
@@ -93,8 +163,9 @@ module Halcyon
93
163
  # <tt>success</tt>
94
164
  #
95
165
  # Returns Hash:{:status=>200, :body=>body}
96
- def ok(body='OK')
97
- {:status => 200, :body => body}
166
+ #
167
+ def ok(body='OK', headers = {})
168
+ {:status => 200, :body => body, :headers => headers}
98
169
  end
99
170
  alias_method :success, :ok
100
171
 
@@ -118,34 +189,93 @@ module Halcyon
118
189
  # <tt>missing</tt>
119
190
  #
120
191
  # Returns Hash:{:status=>404, :body=>body}
121
- def not_found(body='Not Found')
122
- {:status => 404, :body => body}
192
+ #
193
+ def not_found(body='Not Found', headers = {})
194
+ {:status => 404, :body => body, :headers => headers}
123
195
  end
124
196
  alias_method :missing, :not_found
125
-
126
- # Returns the name of the controller in path form.
127
- def self.controller_name
128
- @controller_name ||= self.name.to_const_path
197
+
198
+ #--
199
+ # Filters
200
+ #++
201
+
202
+ class << self
203
+
204
+ # Creates +filters+ accessor method and initializes the +@filters+
205
+ # attribute with the necessary structure.
206
+ #
207
+ def filters
208
+ @filters ||= {:before => [], :after => []}
209
+ end
210
+
211
+ # Sets up filters for the method defined in the controllers.
212
+ #
213
+ # Examples
214
+ #
215
+ # class Foos < Application
216
+ # before :foo do
217
+ # #
218
+ # end
219
+ # after :blah, :only => [:foo]
220
+ # def foo
221
+ # # the block is called before the method is called
222
+ # # and the method is called after the method is called
223
+ # end
224
+ # private
225
+ # def blah
226
+ # #
227
+ # end
228
+ # end
229
+ #
230
+ # Options
231
+ # * +method_or_filter+ either the method to run before
232
+ #
233
+ def before method_or_filter, options={}, &block
234
+ _add_filter(:before, method_or_filter, options, block)
235
+ end
236
+
237
+ # See documentation for the +before+ method.
238
+ #
239
+ def after method_or_filter, options={}, &block
240
+ _add_filter(:after, method_or_filter, options, block)
241
+ end
242
+
243
+ # Used internally to save the filters, applied when called.
244
+ #
245
+ def _add_filter(where, method_or_filter, options, block)
246
+ self.filters[where] << [method_or_filter, options, block]
247
+ end
248
+
129
249
  end
130
-
131
- # Returns the name of the controller in path form.
132
- def controller_name
133
- self.class.controller_name
250
+
251
+ # Used internally.
252
+ #
253
+ # Applies the filters defined by the +before+ and +after+ class methods.
254
+ #
255
+ # +where+ specifies whether to apply <tt>:before</tt> or <tt>:after</tt>
256
+ # filters
257
+ # +action+ the routed action (for testing filter applicability)
258
+ #
259
+ def _run_filters(where, action)
260
+ self.class.filters[where].each do |(method_or_filter, options, block)|
261
+ if block
262
+ block.call(self) if _filter_condition_met?(method_or_filter, options, action)
263
+ else
264
+ send(method_or_filter) if _filter_condition_met?(method_or_filter, options, action)
265
+ end
266
+ end
134
267
  end
135
-
136
- # Generates a URL based on the given name and passed
137
- # options. Used with named routes and resources:
138
- #
139
- # url(:users) # => "/users"
140
- # url(:admin_permissons) # => "/admin/permissions"
141
- # url(:user, @user) # => "/users/1"
142
- #
143
- # Based on the identical method of Merb's controller.
144
- def url(name, rparams={})
145
- Halcyon::Application::Router.generate(name, rparams,
146
- { :controller => controller_name,
147
- :action => method
148
- }
268
+
269
+ # Used internally.
270
+ #
271
+ # Tests whether a filter should be run for an action or not.
272
+ #
273
+ def _filter_condition_met?(method_or_filter, options, action)
274
+ (
275
+ options[:only] and options[:only].include?(action)) or
276
+ (options[:except] and !options[:except].include?(action)
277
+ ) or (
278
+ method_or_filter == action
149
279
  )
150
280
  end
151
281