halcyon 0.4.0 → 0.5.0

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.
Files changed (60) hide show
  1. data/AUTHORS +1 -0
  2. data/LICENSE +20 -0
  3. data/README +107 -0
  4. data/Rakefile +8 -6
  5. data/bin/halcyon +3 -204
  6. data/lib/halcyon.rb +55 -42
  7. data/lib/halcyon/application.rb +247 -0
  8. data/lib/halcyon/application/router.rb +86 -0
  9. data/lib/halcyon/client.rb +187 -35
  10. data/lib/halcyon/client/ssl.rb +38 -0
  11. data/lib/halcyon/controller.rb +154 -0
  12. data/lib/halcyon/exceptions.rb +67 -59
  13. data/lib/halcyon/logging.rb +31 -0
  14. data/lib/halcyon/logging/analogger.rb +31 -0
  15. data/lib/halcyon/logging/helpers.rb +37 -0
  16. data/lib/halcyon/logging/log4r.rb +25 -0
  17. data/lib/halcyon/logging/logger.rb +20 -0
  18. data/lib/halcyon/logging/logging.rb +19 -0
  19. data/lib/halcyon/runner.rb +141 -0
  20. data/lib/halcyon/runner/commands.rb +141 -0
  21. data/lib/halcyon/runner/helpers.rb +9 -0
  22. data/lib/halcyon/runner/helpers/command_helper.rb +71 -0
  23. data/spec/halcyon/application_spec.rb +70 -0
  24. data/spec/halcyon/client_spec.rb +63 -0
  25. data/spec/halcyon/controller_spec.rb +68 -0
  26. data/spec/halcyon/halcyon_spec.rb +63 -0
  27. data/spec/halcyon/logging_spec.rb +31 -0
  28. data/spec/halcyon/router_spec.rb +37 -12
  29. data/spec/halcyon/runner_spec.rb +54 -0
  30. data/spec/spec_helper.rb +75 -9
  31. data/support/generators/halcyon/USAGE +0 -0
  32. data/support/generators/halcyon/halcyon_generator.rb +52 -0
  33. data/support/generators/halcyon/templates/README +26 -0
  34. data/support/generators/halcyon/templates/Rakefile +32 -0
  35. data/support/generators/halcyon/templates/app/application.rb +43 -0
  36. data/support/generators/halcyon/templates/config/config.yml +36 -0
  37. data/support/generators/halcyon/templates/config/init/environment.rb +11 -0
  38. data/support/generators/halcyon/templates/config/init/hooks.rb +39 -0
  39. data/support/generators/halcyon/templates/config/init/requires.rb +10 -0
  40. data/support/generators/halcyon/templates/config/init/routes.rb +50 -0
  41. data/support/generators/halcyon/templates/lib/client.rb +77 -0
  42. data/support/generators/halcyon/templates/runner.ru +8 -0
  43. data/support/generators/halcyon_flat/USAGE +0 -0
  44. data/support/generators/halcyon_flat/halcyon_flat_generator.rb +52 -0
  45. data/support/generators/halcyon_flat/templates/README +26 -0
  46. data/support/generators/halcyon_flat/templates/Rakefile +32 -0
  47. data/support/generators/halcyon_flat/templates/app.rb +49 -0
  48. data/support/generators/halcyon_flat/templates/lib/client.rb +17 -0
  49. data/support/generators/halcyon_flat/templates/runner.ru +8 -0
  50. metadata +73 -20
  51. data/lib/halcyon/client/base.rb +0 -261
  52. data/lib/halcyon/client/exceptions.rb +0 -41
  53. data/lib/halcyon/client/router.rb +0 -106
  54. data/lib/halcyon/server.rb +0 -62
  55. data/lib/halcyon/server/auth/basic.rb +0 -107
  56. data/lib/halcyon/server/base.rb +0 -774
  57. data/lib/halcyon/server/exceptions.rb +0 -41
  58. data/lib/halcyon/server/router.rb +0 -103
  59. data/spec/halcyon/error_spec.rb +0 -55
  60. data/spec/halcyon/server_spec.rb +0 -105
@@ -0,0 +1,63 @@
1
+ #--
2
+ # Start App for Tests
3
+ # and wait for it to be responsive
4
+ #++
5
+
6
+ fork do
7
+ dir = Halcyon.root/'support'/'generators'/'halcyon'/'templates'
8
+ command = "thin start -R runner.ru -p 89981 -c #{dir} > /dev/null 2>&1"
9
+ STDOUT.close
10
+ STDERR.close
11
+ exec command
12
+ end
13
+ client = Halcyon::Client.new('http://localhost:89981')
14
+ begin
15
+ sleep 1.5
16
+ client.get('/time')
17
+ rescue Errno::ECONNREFUSED => e
18
+ retry
19
+ end
20
+
21
+ #--
22
+ # Cleanup
23
+ #++
24
+
25
+ at_exit do
26
+ pids = (`ps auxww | grep support/generators/halcyon/templates`).split("\n").collect{|pid|pid.match(/(\w+)\s+(\w+).+/)[2]}
27
+ pids.each {|pid| Process.kill(9, pid.to_i) rescue nil }
28
+ end
29
+
30
+ #--
31
+ # Tests
32
+ #++
33
+
34
+ describe "Halcyon::Client" do
35
+
36
+ before do
37
+ @client = Halcyon::Client.new('http://localhost:89981')
38
+ end
39
+
40
+ it "should perform requests and return the response values" do
41
+ response = @client.get('/time')[:body]
42
+ response.length.should > 25
43
+ response.include?(Time.now.year.to_s).should.be.true?
44
+ end
45
+
46
+ it "should be able to perform get, post, put, and delete requests" do
47
+ @client.get('/time')[:body].length.should > 25
48
+ @client.post('/time')[:body].length.should > 20
49
+ @client.put('/time')[:body].should == "Not Implemented"
50
+ @client.delete('/time')[:status].should == 501
51
+ end
52
+
53
+ it "should throw exceptions unless an OK response is sent if toggled to" do
54
+ # default behavior is to not raise exceptions
55
+ @client.get('/nonexistent/route')[:status].should == 404
56
+
57
+ # tell it to raise exceptions
58
+ @client.raise_exceptions! true
59
+ should.raise(Halcyon::Exceptions::NotFound) { @client.get('/nonexistent/route') }
60
+ @client.get('/time')[:status].should == 200
61
+ end
62
+
63
+ end
@@ -0,0 +1,68 @@
1
+ describe "Halcyon::Controller" do
2
+
3
+ before do
4
+ @log = ''
5
+ @logger = Logger.new(StringIO.new(@log))
6
+ @config = $config.dup
7
+ @config[:logger] = @logger
8
+ @config[:app] = 'Specs'
9
+ Halcyon.config = @config
10
+ @app = Halcyon::Runner.new
11
+ end
12
+
13
+ it "should provide various shorthand methods for simple responses but take custom response values" do
14
+ controller = Specs.new(Rack::MockRequest.env_for('/'))
15
+
16
+ response = {:status => 200, :body => 'OK'}
17
+ controller.ok.should == response
18
+ controller.success.should == response
19
+
20
+ controller.ok('').should == {:status => 200, :body => ''}
21
+ controller.ok(['OK', 'Sure Thang', 'Correcto']).should == {:status => 200, :body => ['OK', 'Sure Thang', 'Correcto']}
22
+ end
23
+
24
+ it "should provide a quick way to find out what method the request was performed using" do
25
+ %w(GET POST PUT DELETE).each do |m|
26
+ controller = Specs.new(Rack::MockRequest.env_for('/', :method => m))
27
+ controller.method.should == m.downcase.to_sym
28
+ end
29
+ end
30
+
31
+ it "should provide convenient access to GET and POST data" do
32
+ controller = Specs.new(Rack::MockRequest.env_for("/#{rand}?foo=bar"))
33
+ controller.get[:foo].should == 'bar'
34
+
35
+ controller = Specs.new(Rack::MockRequest.env_for("/#{rand}", :method => 'POST', :input => {:foo => 'bar'}.to_params))
36
+ controller.post[:foo].should == 'bar'
37
+ end
38
+
39
+ it "should parse URI query params correctly" do
40
+ controller = Specs.new(Rack::MockRequest.env_for("/?query=value&lang=en-US"))
41
+ controller.get[:query].should == 'value'
42
+ controller.get[:lang].should == 'en-US'
43
+ end
44
+
45
+ it "should parse the URI correctly" do
46
+ controller = Specs.new(Rack::MockRequest.env_for("http://localhost:4000/slaughterhouse/5"))
47
+ controller.uri.should == '/slaughterhouse/5'
48
+
49
+ controller = Specs.new(Rack::MockRequest.env_for("/slaughterhouse/5"))
50
+ controller.uri.should == '/slaughterhouse/5'
51
+
52
+ controller = Specs.new(Rack::MockRequest.env_for(""))
53
+ controller.uri.should == '/'
54
+ end
55
+
56
+ it 'should provide url accessor for resource index route' do
57
+ controller = Resources.new(Rack::MockRequest.env_for("/resources"))
58
+ controller.uri.should == controller.url(:resources)
59
+ end
60
+
61
+ it 'should provide url accessor for resource show route' do
62
+ resource = Model.new
63
+ resource.id = 1
64
+ controller = Resources.new(Rack::MockRequest.env_for("/resources/1"))
65
+ controller.uri.should == controller.url(:resource, resource)
66
+ end
67
+
68
+ end
@@ -0,0 +1,63 @@
1
+ describe "Halcyon" do
2
+
3
+ before do
4
+ @log = ''
5
+ @logger = Logger.new(StringIO.new(@log))
6
+ @config = $config.dup
7
+ @config[:logger] = @logger
8
+ @config[:app] = 'Specs'
9
+ Halcyon.config = @config
10
+ @app = Halcyon::Runner.new
11
+ end
12
+
13
+ it "should provide the path of the application root directory" do
14
+ Halcyon.root.should == Dir.pwd
15
+ end
16
+
17
+ it "should provide quick access to the configuration hash" do
18
+ Halcyon.config.is_a?(Hash).should.be.true?
19
+ end
20
+
21
+ it "should provide environment label" do
22
+ Halcyon.environment.should == :development
23
+ Halcyon.environment.should == Halcyon.config[:environment]
24
+ end
25
+
26
+ it "should provide universal access to a logger" do
27
+ # We assume Logger here because, you know, we're gods of the test
28
+ Halcyon.logger.is_a?(Logger).should.be.true?
29
+ # And this is just a side affect of making the logger universally accessible
30
+ {}.logger.is_a?(Logger).should.be.true?
31
+ end
32
+
33
+ it "should provide the (estimated) application name" do
34
+ # We set this above
35
+ Halcyon.app.should == "Specs"
36
+ end
37
+
38
+ it "should provide sane default paths for essential components" do
39
+ Halcyon.paths.is_a?(Hash).should.be.true?
40
+ Halcyon.paths[:controller].should == Halcyon.root/"app"
41
+ Halcyon.paths[:lib].should == Halcyon.root/"lib"
42
+ Halcyon.paths[:config].should == Halcyon.root/"config"
43
+ Halcyon.paths[:init].should == Halcyon.root/"config"/"{init,initialize}"
44
+ Halcyon.paths[:log].should == Halcyon.root/"log"
45
+ end
46
+
47
+ it "should provide configurable attribute definition for quick access to specific configuration values" do
48
+ test_method = "oracle"
49
+ method_count = Halcyon.methods.length
50
+ Halcyon.configurable("oracle")
51
+ (Halcyon.methods.length - method_count).should == 2
52
+ Halcyon.method(test_method.to_sym).is_a?(Method).should.be.true?
53
+ Halcyon.method("#{test_method}=".to_sym).is_a?(Method).should.be.true?
54
+ Halcyon.send("#{test_method}=".to_sym, 10)
55
+ Halcyon.send(test_method).should == Halcyon.config[test_method.to_sym]
56
+ end
57
+
58
+ it "should predefine quick access to the 'db' configuration value" do
59
+ Halcyon.db = 100
60
+ Halcyon.db.should == Halcyon.config[:db]
61
+ end
62
+
63
+ end
@@ -0,0 +1,31 @@
1
+ describe "Halcyon::Logging" do
2
+
3
+ it "should set the default logger when none specified" do
4
+ Halcyon.send(:remove_const, :Logger)
5
+ Halcyon::Logging.set
6
+ Halcyon::Logger.ancestors.include?(::Logger).should.be.true?
7
+ end
8
+
9
+ it "should set the logger type specified" do
10
+ Halcyon.send(:remove_const, :Logger)
11
+ Halcyon::Logging.set('Logger')
12
+ Halcyon::Logger.ancestors.include?(::Logger).should.be.true?
13
+
14
+ # Not running these because the above test is equivalent as well as
15
+ # throwing errors for folks who do not have Logging, Log4r, and
16
+ # Analogger installed.
17
+
18
+ # Halcyon.send(:remove_const, :Logger)
19
+ # Halcyon::Logging.set('Analogger')
20
+ # Halcyon::Logger.ancestors.include?(::Swiftcore::Analogger::Client).should.be.true?
21
+ #
22
+ # Halcyon.send(:remove_const, :Logger)
23
+ # Halcyon::Logging.set('Log4r')
24
+ # Halcyon::Logger.ancestors.include?(::Log4r::Logger).should.be.true?
25
+ #
26
+ # Halcyon.send(:remove_const, :Logger)
27
+ # Halcyon::Logging.set('Logging')
28
+ # Halcyon::Logger.ancestors.include?(::Logging::Logger).should.be.true?
29
+ end
30
+
31
+ end
@@ -1,32 +1,57 @@
1
- describe "Halcyon::Server::Router" do
1
+ describe "Halcyon::Application::Router" do
2
2
 
3
3
  before do
4
- @app = Specr.new :port => 4000
4
+ @log = ''
5
+ @logger = Logger.new(StringIO.new(@log))
6
+ @config = $config.dup
7
+ @config[:logger] = @logger
8
+ Halcyon.config = @config
9
+ @app = Halcyon::Runner.new
5
10
  end
6
11
 
7
- it "should prepares routes correctly when written correctly" do
12
+ it "should prepare routes correctly when written correctly" do
8
13
  # routes have been defined for Specr
9
- Halcyon::Server::Router.routes.should.not == []
10
- Halcyon::Server::Router.routes.length.should > 0
14
+ Halcyon::Application::Router.routes.should.not == []
15
+ Halcyon::Application::Router.routes.length.should > 0
11
16
  end
12
17
 
13
18
  it "should match URIs to the correct route" do
14
- Halcyon::Server::Router.route(Rack::MockRequest.env_for('/'))[:action].should == 'index'
19
+ request = Rack::Request.new(Rack::MockRequest.env_for('/'))
20
+ Halcyon::Application::Router.route(request)[:action].should == 'index'
15
21
  end
16
22
 
17
23
  it "should use the default route if no matching route is found" do
18
- Halcyon::Server::Router.route(Rack::MockRequest.env_for('/erroneous/path'))[:action].should == 'not_found'
19
- Halcyon::Server::Router.route(Rack::MockRequest.env_for("/random/#{rand}"))[:action].should == 'not_found'
24
+ # missing instead of not_found because we gave a different default route
25
+ request = Rack::Request.new(Rack::MockRequest.env_for("/erroneous/path/#{rand}/#{rand}"))
26
+ Halcyon::Application::Router.route(request)[:action].should == 'missing'
27
+
28
+ request = Rack::Request.new(Rack::MockRequest.env_for("/random/#{rand}/#{rand}"))
29
+ Halcyon::Application::Router.route(request)[:action].should == 'missing'
20
30
  end
21
31
 
22
32
  it "should map params in routes to parameters" do
23
- response = Halcyon::Server::Router.route(Rack::MockRequest.env_for('/hello/Matt'))
24
- response[:action].should == 'greeter'
25
- response[:name].should == 'Matt'
33
+ request = Rack::Request.new(Rack::MockRequest.env_for('/hello/Matt'))
34
+ response = Halcyon::Application::Router.route(request)
35
+ response[:action].should == 'greeter'
36
+ response[:name].should == 'Matt'
26
37
  end
27
38
 
28
39
  it "should supply arbitrary routing param values included as a param even if not in the URI" do
29
- Halcyon::Server::Router.route(Rack::MockRequest.env_for('/'))[:arbitrary].should == 'random'
40
+ request = Rack::Request.new(Rack::MockRequest.env_for('/'))
41
+ request.env['rack.input'] << "arbitrary=random"
42
+ Halcyon::Application::Router.route(request)[:arbitrary].should == 'random'
43
+ end
44
+
45
+ it "should match index method of resources" do
46
+ index_req = Rack::Request.new(Rack::MockRequest.env_for('/resources'))
47
+ Halcyon::Application::Router.route(index_req)[:controller].should == 'resources'
48
+ Halcyon::Application::Router.route(index_req)[:action].should == 'index'
49
+ end
50
+
51
+ it "should match show method of resource" do
52
+ show_req = Rack::Request.new(Rack::MockRequest.env_for('/resources/id'))
53
+ Halcyon::Application::Router.route(show_req)[:controller].should == 'resources'
54
+ Halcyon::Application::Router.route(show_req)[:action].should == 'show'
30
55
  end
31
56
 
32
57
  end
@@ -0,0 +1,54 @@
1
+ module Kernel
2
+ alias_method :__warn, :warn
3
+ def warn(msg)
4
+ $warning = msg
5
+ __warn(msg) if $do_warns
6
+ end
7
+ end
8
+
9
+ describe "Halcyon::Runner" do
10
+
11
+ before do
12
+ @log = ''
13
+ @logger = Logger.new(StringIO.new(@log))
14
+ @config = $config.dup
15
+ @config[:logger] = @logger
16
+ Halcyon.config = @config
17
+ @app = Halcyon::Runner.new
18
+ end
19
+
20
+ it "should warn if a non-existent config file is loaded" do
21
+ $do_warns = false
22
+ path = Halcyon.root/'config'/'config.yml'
23
+ Halcyon::Runner.load_config(path).nil?.should == true
24
+ $warning.should =~ %r{#{path} not found}
25
+ $do_warns = true
26
+ end
27
+
28
+ it "should set up logging according to configuration" do
29
+ time = Time.now.to_s
30
+ @app.logger.debug "Test message for #{time}"
31
+ @log.should =~ /Test message for #{time}/
32
+ end
33
+
34
+ it "should recognize what application it is running as" do
35
+ # Without setting explicitly in config
36
+ Halcyon.app.should == Halcyon.root.split('/').last.camel_case
37
+
38
+ # With setting explicitly in config
39
+ Halcyon.config[:app] = 'Specr'
40
+ Halcyon::Runner.new
41
+ Halcyon.app.should == 'Specr'
42
+
43
+ # Setting directly
44
+ Halcyon.app = 'Specr2'
45
+ Halcyon.app.should == 'Specr2'
46
+ end
47
+
48
+ it "should proxy calls to Halcyon::Application" do
49
+ status, headers, body = @app.call(Rack::MockRequest.env_for('/'))
50
+ status.should == 200
51
+ body.body[0].should == Specs.new(Rack::MockRequest.env_for('/')).send(:index).to_json
52
+ end
53
+
54
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,21 +1,87 @@
1
- require 'halcyon/server'
1
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'halcyon')
2
2
  require 'rack/mock'
3
+ require 'logger'
3
4
 
4
- $test = true
5
+ # Default Settings
5
6
 
6
- class Specr < Halcyon::Server::Base
7
+ $config = {
8
+ :allow_from => :all,
9
+ :environment => :development,
10
+ :logger => nil,
11
+ :logging => {
12
+ :level => 'debug'
13
+ }
14
+ }
15
+
16
+ # Testing Application
17
+
18
+ # Default controller
19
+ class Application < Halcyon::Controller; end
20
+
21
+ # Weird edge-case controller
22
+ class Specs < Application
7
23
 
8
- route do |r|
9
- r.match('/hello/:name').to(:action => 'greeter')
10
- r.match('/').to(:action => 'index', :arbitrary => 'random')
24
+ def greeter
25
+ ok("Hello #{params[:name]}")
11
26
  end
12
27
 
13
- def index(params)
28
+ def index
14
29
  ok('Found')
15
30
  end
16
31
 
17
- def greeter(params)
18
- ok("Hello #{params[:name]}")
32
+ def cause_exception
33
+ raise Exception.new("Oops!")
34
+ end
35
+
36
+ def call_nonexistent_method
37
+ hash = Hash.new
38
+ hash.please_dont_exist_and_please_throw_no_method_error
39
+ ok
19
40
  end
20
41
 
42
+ private
43
+
44
+ def undispatchable_private_method
45
+ "it's private, so it won't be found by the dispatcher"
46
+ end
47
+
48
+ end
49
+
50
+ # Resources controller
51
+ class Resources < Application
52
+
53
+ def index
54
+ ok('List of resources')
55
+ end
56
+
57
+ def show
58
+ ok("One resource: #{params[:id]}")
59
+ end
60
+ end
61
+
62
+ # Models
63
+
64
+ class Model
65
+ attr_accessor :id
66
+ end
67
+
68
+ # Environment
69
+
70
+ Halcyon.configurable_attr(:environment)
71
+
72
+ # Testing routes
73
+
74
+ Halcyon::Application.route do |r|
75
+ r.resources :resources
76
+
77
+ r.match('/hello/:name').to(:controller => 'specs', :action => 'greeter')
78
+ r.match('/:action').to(:controller => 'specs')
79
+ r.match('/:controller/:action').to()
80
+ r.match('/').to(:controller => 'specs', :action => 'index', :arbitrary => 'random')
81
+ # r.default_routes
82
+ {:action => 'missing'}
83
+ end
84
+
85
+ Halcyon::Application.startup do |config|
86
+ $started = true
21
87
  end