halcyon 0.5.0 → 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -95,8 +95,9 @@ module Halcyon
95
95
  status, body = http_status
96
96
  class_eval <<-"end;"
97
97
  class #{body.gsub(/( |\-)/,'')} < Halcyon::Exceptions::Base
98
- def initialize(s=#{status}, b='#{body}')
99
- super
98
+ def initialize(body=nil)
99
+ body = '#{body}' if body.nil?
100
+ super(#{status}, body)
100
101
  end
101
102
  end
102
103
  end;
@@ -4,6 +4,7 @@ module Halcyon
4
4
  # * setting up the logger
5
5
  # * loading initializers
6
6
  # * loading controllers
7
+ # All of which is done by the call to <tt>Halcyon::Application.boot</tt>.
7
8
  #
8
9
  # The Runner is a full-fledged Rack application, and accepts calls to #call.
9
10
  #
@@ -14,70 +15,14 @@ module Halcyon
14
15
  # Halcyon::Runner.run!(['start', '-p', '4647'])
15
16
  #
16
17
  # # load the config file and initialize the app
17
- # Halcyon::Runner.load_config Halcyon.root/'config'/'config.yml'
18
18
  # Halcyon::Runner.new
19
19
  class Runner
20
20
 
21
21
  autoload :Commands, 'halcyon/runner/commands'
22
22
 
23
- class << self
24
-
25
- # Runs commands from the CLI.
26
- # +argv+ the arguments to pass to the commands
27
- #
28
- # Returns nothing
29
- def run!(argv=ARGV)
30
- Commands.send(argv.shift, argv)
31
- end
32
-
33
- # Returns the path to the configuration file specified, defaulting
34
- # to the path for the <tt>config.yml</tt> file.
35
- # +file+ the name of the config file path (without the <tt>.yml</tt>
36
- # extension)
37
- def config_path(file = "config")
38
- Halcyon.paths[:config]/"#{file}.yml"
39
- end
40
-
41
- end
42
-
43
23
  # Initializes the application and application resources.
44
- def initialize
45
- Halcyon::Runner.load_paths if Halcyon.paths.nil?
46
-
47
- # Load the configuration if none is set already
48
- if Halcyon.config.nil?
49
- if File.exist?(Halcyon::Runner.config_path)
50
- Halcyon.config = Halcyon::Runner.load_config
51
- else
52
- Halcon.config = Halcyon::Application::DEFAULT_OPTIONS
53
- end
54
- end
55
-
56
- # Set application name
57
- Halcyon.app = Halcyon.config[:app] || Halcyon.root.split('/').last.camel_case
58
-
59
- # Setup logger
60
- if Halcyon.config[:logger]
61
- Halcyon.config[:logging] = (Halcyon.config[:logging] || Halcyon::Application::DEFAULT_OPTIONS[:logging]).merge({
62
- :type => Halcyon.config[:logger].class.to_s,
63
- :logger => Halcyon.config[:logger]
64
- })
65
- end
66
- Halcyon::Logging.set((Halcyon.config[:logging][:type] rescue nil))
67
- Halcyon.logger = Halcyon::Logger.setup(Halcyon.config[:logging])
68
-
69
- # Run initializers
70
- Dir.glob(Halcyon.paths[:init]/'{requires,hooks,routes,environment,*}.rb').each do |initializer|
71
- self.logger.debug "Init: #{File.basename(initializer).chomp('.rb').camel_case}" if
72
- require initializer.chomp('.rb')
73
- end
74
-
75
- # Setup autoloads for Controllers found in Halcyon.root/'app'
76
- Dir.glob(Halcyon.paths[:controller]/'{application,*}.rb').each do |controller|
77
- self.logger.debug "Load: #{File.basename(controller).chomp('.rb').camel_case} Controller" if
78
- require controller.chomp('.rb')
79
- end
80
-
24
+ def initialize(&block)
25
+ Halcyon::Application.boot(&block) unless Halcyon::Application.booted
81
26
  @app = Halcyon::Application.new
82
27
  end
83
28
 
@@ -91,48 +36,12 @@ module Halcyon
91
36
 
92
37
  class << self
93
38
 
94
- # Loads the configuration file specified into <tt>Halcyon.config</tt>.
95
- # +file+ the configuration file to load
96
- #
97
- # Examples
98
- # Halcyon::Runner.load_config Halcyon.root/'config'/'config.yml'
99
- # Halcyon.config #=> {:allow_from => :all, :logging => {...}, ...}.to_mash
100
- #
101
- # Returns {Symbol:key => String:value}.to_mash
102
- def load_config(file=Halcyon::Runner.config_path)
103
- if File.exist?(file)
104
- require 'yaml'
105
-
106
- # load the config file
107
- begin
108
- config = YAML.load_file(file).to_mash
109
- rescue Errno::EACCES
110
- raise LoadError.new("Can't access #{file}, try 'sudo #{$0}'")
111
- end
112
- else
113
- warn "#{file} not found, ensure the path to this file is correct. Ignoring."
114
- nil
115
- end
116
- end
117
-
118
- # Set the paths for resources to be located.
119
- #
120
- # Used internally for setting the load paths if not manually overridden
121
- # and needed to be set before normal application initialization.
122
- #
123
- # TODO: Move this to the planned <tt>Halcyon::Config</tt> object.
39
+ # Runs commands from the CLI.
40
+ # +argv+ the arguments to pass to the commands
124
41
  #
125
- # Returns nothing.
126
- def load_paths
127
- # Set the default application paths, not overwriting manually set paths
128
- Halcyon.paths = {
129
- :controller => Halcyon.root/'app',
130
- :model => Halcyon.root/'app'/'models',
131
- :lib => Halcyon.root/'lib',
132
- :config => Halcyon.root/'config',
133
- :init => Halcyon.root/'config'/'{init,initialize}',
134
- :log => Halcyon.root/'log'
135
- }.to_mash.merge(Halcyon.paths || {})
42
+ # Returns nothing
43
+ def run!(argv=ARGV)
44
+ Commands.send(argv.shift, argv)
136
45
  end
137
46
 
138
47
  end
@@ -63,10 +63,9 @@ module Halcyon
63
63
  # Set up the application
64
64
  Object.instance_eval do
65
65
  $log = ''
66
- Halcyon::Runner.load_paths if Halcyon.paths.nil?
67
- (Halcyon.config = Halcyon::Runner.load_config) || require(Halcyon.root/'app')
68
- Halcyon.config[:logger] = Logger.new(StringIO.new($log))
69
- $app = Halcyon::Runner.new
66
+ $app = Halcyon::Runner.new do |c|
67
+ c[:logger] = Logger.new(StringIO.new($log))
68
+ end
70
69
  $response = nil
71
70
  end
72
71
 
data/lib/rack/jsonp.rb ADDED
@@ -0,0 +1,38 @@
1
+ module Rack
2
+
3
+ # A Rack middleware for providing JSON-P support.
4
+ #
5
+ # Full credit to Flinn Mueller (http://actsasflinn.com/) for this contribution.
6
+ #
7
+ class JSONP
8
+
9
+ def initialize(app)
10
+ @app = app
11
+ end
12
+
13
+ # Proxies the request to the application, stripping out the JSON-P callback
14
+ # method and padding the response with the appropriate callback format.
15
+ #
16
+ # Changes nothing if no <tt>callback</tt> param is specified.
17
+ #
18
+ def call(env)
19
+ status, headers, response = @app.call(env)
20
+ request = Rack::Request.new(env)
21
+ response = pad(request.params.delete('callback'), response) if request.params.include?('callback')
22
+ [status, headers, response]
23
+ end
24
+
25
+ # Pads the response with the appropriate callback format according to the
26
+ # JSON-P spec/requirements.
27
+ #
28
+ # The Rack response spec indicates that it should be enumerable. The method
29
+ # of combining all of the data into a sinle string makes sense since JSON
30
+ # is returned as a full string.
31
+ #
32
+ def pad(callback, response, body = "")
33
+ response.each{ |s| body << s }
34
+ "#{callback}(#{body})"
35
+ end
36
+
37
+ end
38
+ end
@@ -3,10 +3,9 @@ describe "Halcyon::Application" do
3
3
  before do
4
4
  @log = ''
5
5
  @logger = Logger.new(StringIO.new(@log))
6
- @config = $config.dup
7
- @config[:logger] = @logger
8
- @config[:app] = 'Specs'
9
- Halcyon.config = @config
6
+ Halcyon.config.use do |c|
7
+ c[:logger] = @logger
8
+ end
10
9
  @app = Halcyon::Runner.new
11
10
  end
12
11
 
@@ -17,7 +16,7 @@ describe "Halcyon::Application" do
17
16
 
18
17
  it "should dispatch methods according to their respective routes" do
19
18
  Rack::MockRequest.new(@app).get("/hello/Matt")
20
- @log.should =~ / INFO \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] \(\d+\) Specs :: \[200\] \/hello\/Matt \(.+\)\n/
19
+ $hello.should == "Matt"
21
20
  end
22
21
 
23
22
  it "should handle requests and respond with JSON" do
@@ -26,13 +25,6 @@ describe "Halcyon::Application" do
26
25
  body['body'].should == "Found"
27
26
  end
28
27
 
29
- it "should handle requests with param values in the URL" do
30
- body = JSON.parse(Rack::MockRequest.new(@app).get("/hello/Matt?test=value").body)
31
- body['status'].should == 200
32
- body['body'].should == "Hello Matt"
33
- @log.split("\n").last.should =~ /"test"=>"value"/
34
- end
35
-
36
28
  it "should not dispatch private methods" do
37
29
  body = JSON.parse(Rack::MockRequest.new(@app).get("/specs/undispatchable_private_method").body)
38
30
  body['status'].should == 404
@@ -47,12 +39,14 @@ describe "Halcyon::Application" do
47
39
 
48
40
  it "should log activity" do
49
41
  Halcyon.logger.is_a?(Logger).should.be.true?
50
- Rack::MockRequest.new(@app).get("/lolcats/r/cute")
51
- @log.should =~ / INFO \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] \(\d+\) Specs :: \[404\] \/lolcats\/r\/cute \(.+\)\n/
42
+ # Rack::MockRequest.new(@app).get("/lolcats/r/cute")
43
+ # @log.should =~ / INFO \[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] \(\d+\) Specs :: \[404\] \/lolcats\/r\/cute \(.+\)\n/
44
+ # Halcyon.logger.debug "Testing"
45
+ # @log.should =~ /Testing/
52
46
  end
53
47
 
54
48
  it "should allow all requests by default" do
55
- Halcyon.config[:allow_from].should == :all
49
+ Halcyon.config[:allow_from].to_sym.should == :all
56
50
  end
57
51
 
58
52
  it "should handle exceptions gracefully" do
@@ -0,0 +1,157 @@
1
+ describe "Halcyon::Config" do
2
+
3
+ before do
4
+ Halcyon.config = Halcyon::Config.new(:environment => :test)
5
+ # @log = ''
6
+ # @logger = Logger.new(StringIO.new(@log))
7
+ # @config = $config.dup
8
+ # @config[:logger] = @logger
9
+ # @config[:app] = 'Specs'
10
+ # Halcyon.config = @config
11
+ # @app = Halcyon::Runner.new
12
+ end
13
+
14
+ it "should provide the path of the application root directory" do
15
+ Halcyon.root.should == Dir.pwd
16
+ Halcyon.config[:root] = Dir.pwd
17
+ Halcyon.root.should == Halcyon.config[:root]
18
+ end
19
+
20
+ it "should provide numerous ways to retrieve configuration values" do
21
+ Halcyon.config[:foo] = :bar
22
+
23
+ Halcyon.config[:foo].should == :bar
24
+ Halcyon.config.foo.should == :bar
25
+ Halcyon.config.get(:foo).should == :bar
26
+ Halcyon.config.use {|c| c[:foo].should == :bar }
27
+ end
28
+
29
+ it "should provide numerous ways to set configuration values" do
30
+ Halcyon.config[:foo] = :a; Halcyon.config[:foo].should == :a
31
+ Halcyon.config.foo = :b; Halcyon.config[:foo].should == :b
32
+ Halcyon.config.put(:foo, :c); Halcyon.config[:foo].should == :c
33
+ Halcyon.config.put(:foo => :d); Halcyon.config[:foo].should == :d
34
+ Halcyon.config.use {|c| c[:foo] = :e }; Halcyon.config[:foo].should == :e
35
+ end
36
+
37
+ it "should access to major config values through Halcyon.<attr> accessors" do
38
+ Halcyon.config.use do |c|
39
+ c[:app] = "Specr"
40
+ c[:root] = Dir.pwd
41
+ c[:paths] = Halcyon::Config::Paths.new
42
+ c[:db] = Mash.new(:test => Mash.new)
43
+ c[:environment] = :test
44
+ end
45
+
46
+ Halcyon.app.should == Halcyon.config[:app]
47
+ Halcyon.root.should == (Halcyon.config[:root] || Dir.pwd)
48
+ Halcyon.paths.should == Halcyon.config[:paths]
49
+ Halcyon.db.should == Halcyon.config[:db]
50
+ Halcyon.environment.should == Halcyon.config[:environment]
51
+ end
52
+
53
+ it "should provide custom Halcyon.<attr> accessors interfacing config values" do
54
+ Halcyon.configurable_attr(:foo)
55
+ Halcyon.foo = true
56
+ Halcyon.foo.should == Halcyon.config[:foo]
57
+
58
+ Halcyon.configurable_reader(:bar) do
59
+ Halcyon.config[:bar].to_sym
60
+ end
61
+ Halcyon.config[:bar] = "foo"
62
+ Halcyon.bar.should == :foo
63
+
64
+ Halcyon.configurable_reader(:baz, "Halcyon.config[%s].to_sym")
65
+ Halcyon.config[:baz] = "bar"
66
+ Halcyon.baz.should == :bar
67
+
68
+ Halcyon.configurable_writer(:bing) do |value|
69
+ Halcyon.config[:bing] = value.to_sym
70
+ end
71
+ Halcyon.bing = "foo"
72
+ Halcyon.config[:bing].should == :foo
73
+
74
+ Halcyon.configurable_writer(:bong, "Halcyon.config[%s] = value.to_sym")
75
+ Halcyon.bong = "bar"
76
+ Halcyon.config[:bong].should == :bar
77
+ end
78
+
79
+ # it "should ..." do
80
+ # #
81
+ # end
82
+
83
+ end
84
+
85
+ describe "Halcyon::Config::Paths" do
86
+
87
+ before do
88
+ @paths = Halcyon::Config::Paths.new
89
+ end
90
+
91
+ it "should be able to look up paths by name" do
92
+ @paths.for(:config).should == Halcyon.root/'config'
93
+ end
94
+
95
+ it "should be able to define new paths" do
96
+ @paths.define(:foo => Halcyon.root/'bar', :bar => Halcyon.root/'foo')
97
+ @paths.for(:foo).should == Halcyon.root/'bar'
98
+ @paths.for(:bar).should == Halcyon.root/'foo'
99
+ end
100
+
101
+ it "should be able to define new paths by name" do
102
+ @paths.define(:foo, Halcyon.root/'baz')
103
+ @paths.for(:foo).should == Halcyon.root/'baz'
104
+ end
105
+
106
+ it "should provide a shortcut to look up paths by name" do
107
+ @paths[:config].should == @paths.for(:config)
108
+ end
109
+
110
+ it "should provide a shortcut to define new paths by name" do
111
+ @paths[:config] = Halcyon.root/'gifnoc'
112
+ @paths.for(:config).should == Halcyon.root/'gifnoc'
113
+ end
114
+
115
+ it "should raise an ArgumentError if a nonexistent path is queried for" do
116
+ should.raise(ArgumentError) { @paths.for(:nonexistent_path) }
117
+ end
118
+
119
+ end
120
+
121
+ require 'tmpdir'
122
+ require 'yaml'
123
+ describe "Halcyon::Config::File" do
124
+
125
+ before do
126
+ @config = {
127
+ :allow_from => "all",
128
+ :logging => {
129
+ :type => "Logger",
130
+ :level => "debug"
131
+ },
132
+ :root => Dir.pwd,
133
+ :app => "Specr",
134
+ :environment => "development"
135
+ }.to_mash
136
+ File.open(Dir.tmpdir/'config.yaml', 'w+'){|f| f << @config.to_yaml }
137
+ File.open(Dir.tmpdir/'config.json', 'w+'){|f| f << @config.to_json }
138
+ end
139
+
140
+ it "should load the configuration from the YAML config file" do
141
+ Halcyon::Config::File.new(Dir.tmpdir/'config.yaml').to_hash.should == @config
142
+ end
143
+
144
+ it "should load the configuration from the JSON config file" do
145
+ Halcyon::Config::File.new(Dir.tmpdir/'config.json').to_hash(:from_json).should == @config
146
+ end
147
+
148
+ it "should provide shortcuts for loading configuration files" do
149
+ Halcyon::Config::File.load(Dir.tmpdir/'config.yaml').should == @config
150
+ Halcyon::Config::File.load_from_json(Dir.tmpdir/'config.json')
151
+ end
152
+
153
+ it "should throw an ArgumentError when the config file doesn't exist" do
154
+ should.raise(ArgumentError) { Halcyon::Config::File.load('/path/to/nonexistent/file.yalm') }
155
+ end
156
+
157
+ end
@@ -3,22 +3,39 @@ describe "Halcyon::Controller" do
3
3
  before do
4
4
  @log = ''
5
5
  @logger = Logger.new(StringIO.new(@log))
6
- @config = $config.dup
7
- @config[:logger] = @logger
8
- @config[:app] = 'Specs'
9
- Halcyon.config = @config
6
+ Halcyon.config.use do |c|
7
+ c[:logger] = @logger
8
+ end
10
9
  @app = Halcyon::Runner.new
11
10
  end
12
11
 
13
12
  it "should provide various shorthand methods for simple responses but take custom response values" do
14
13
  controller = Specs.new(Rack::MockRequest.env_for('/'))
15
14
 
16
- response = {:status => 200, :body => 'OK'}
15
+ response = {:status => 200, :body => 'OK', :headers => {}}
17
16
  controller.ok.should == response
18
17
  controller.success.should == response
19
18
 
20
- controller.ok('').should == {:status => 200, :body => ''}
21
- controller.ok(['OK', 'Sure Thang', 'Correcto']).should == {:status => 200, :body => ['OK', 'Sure Thang', 'Correcto']}
19
+ controller.ok('').should == {:status => 200, :body => '', :headers => {}}
20
+ controller.ok(['OK', 'Sure Thang', 'Correcto']).should == {:status => 200, :body => ['OK', 'Sure Thang', 'Correcto'], :headers => {}}
21
+
22
+ headers = {'Date' => Time.now.strftime("%a, %d %h %Y %H:%I:%S %Z"), 'Test' => 'FooBar'}
23
+ controller.ok('OK', headers).should == {:status => 200, :body => 'OK', :headers => headers}
24
+ end
25
+
26
+ it "should provide extensive responders" do
27
+ controller = Specs.new(Rack::MockRequest.env_for('/'))
28
+
29
+ should.raise(Halcyon::Exceptions::UnprocessableEntity) { controller.status(:unprocessable_entity) }
30
+ response = Rack::MockRequest.new(@app).get("/specs/unprocessable_entity_test")
31
+ response.body.should =~ %r{Unprocessable Entity}
32
+
33
+ should.raise(Halcyon::Exceptions::UnprocessableEntity) { controller.status(:unprocessable_entity) }
34
+ end
35
+
36
+ it "should indicate service is unavailable if an status specified is not found" do
37
+ controller = Specs.new(Rack::MockRequest.env_for('/'))
38
+ should.raise(Halcyon::Exceptions::ServiceUnavailable) { controller.status(:this_state_does_not_exist) }
22
39
  end
23
40
 
24
41
  it "should provide a quick way to find out what method the request was performed using" do
@@ -52,17 +69,59 @@ describe "Halcyon::Controller" do
52
69
  controller = Specs.new(Rack::MockRequest.env_for(""))
53
70
  controller.uri.should == '/'
54
71
  end
55
-
56
- it 'should provide url accessor for resource index route' do
72
+
73
+ it "should provide url accessor for resource index route" do
57
74
  controller = Resources.new(Rack::MockRequest.env_for("/resources"))
58
75
  controller.uri.should == controller.url(:resources)
59
76
  end
60
-
61
- it 'should provide url accessor for resource show route' do
77
+
78
+ it "should provide url accessor for resource show route" do
62
79
  resource = Model.new
63
80
  resource.id = 1
64
81
  controller = Resources.new(Rack::MockRequest.env_for("/resources/1"))
65
82
  controller.uri.should == controller.url(:resource, resource)
66
83
  end
67
84
 
85
+ it "should accept response headers" do
86
+ controller = Specs.new(Rack::MockRequest.env_for(""))
87
+ headers = {'Date' => Time.now.strftime("%a, %d %h %Y %H:%I:%S %Z"), 'Foo' => 'Bar'}
88
+ controller.ok('OK', headers).should == {:status => 200, :body => 'OK', :headers => headers}
89
+ controller.ok('OK').should == {:status => 200, :body => 'OK', :headers => {}}
90
+
91
+ response = Rack::MockRequest.new(@app).get("/goob")
92
+ response['Date'].should == Time.now.strftime("%a, %d %h %Y %H:%I:%S %Z")
93
+ response['Content-Language'].should == 'en'
94
+ end
95
+
96
+ it "should handle return values from actions not from the response helpers" do
97
+ {
98
+ 'OK' => {'status' => 200, 'body' => 'OK'},
99
+ [200, {}, 'OK'] => {'status' => 200, 'body' => 'OK'},
100
+ [1, 2, 3] => {'status' => 200, 'body' => [1, 2, 3]},
101
+ {'foo' => 'bar'} => {'status' => 200, 'body' => {'foo' => 'bar'}}
102
+ }.each do |(value, expected)|
103
+ $return_value_for_gaff = value
104
+ response = JSON.parse(Rack::MockRequest.new(@app).get("/gaff").body).should == expected
105
+ end
106
+ end
107
+
108
+ it "should run filters before or after actions" do
109
+ response = Rack::MockRequest.new(@app).get("/index")
110
+ response.body.should =~ %r{Found}
111
+
112
+ # The Accepted exception is raised if +cause_exception_in_filter+ is set
113
+ response = Rack::MockRequest.new(@app).get("/index?cause_exception_in_filter=true")
114
+ response.body.should =~ %r{Accepted}
115
+
116
+ response = Rack::MockRequest.new(@app).get("/foobar")
117
+ response.body.should =~ %r{fubr}
118
+
119
+ # The Created exception is raised if +cause_exception_in_filter+ is set
120
+ response = Rack::MockRequest.new(@app).get("/foobar?cause_exception_in_filter=true")
121
+ response.body.should =~ %r{Created}
122
+
123
+ response = Rack::MockRequest.new(@app).get("/hello/Matt?cause_exception_in_filter_block=true")
124
+ response.body.should =~ %r{Not Found}
125
+ end
126
+
68
127
  end