halcyon 0.5.0 → 0.5.1

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.
@@ -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