httparrot 0.0.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.
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in httparrot.gemspec
4
+ gemspec
5
+
6
+ platforms :jruby do
7
+ gem 'jruby-openssl'
8
+ end
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec"
3
+ require "rspec/core/rake_task"
4
+
5
+ RSpec::Core::RakeTask.new(:spec) do |t|
6
+ t.rspec_opts = "--color"
7
+ end
8
+
9
+ task :default => [:spec]
data/Readme.rdoc ADDED
@@ -0,0 +1,97 @@
1
+ = HTTParrot
2
+
3
+ Do you need to mock an API endpoint over an HTTP connection? Does your API only speak HTTP? Other things and stuff related to mock servers and HTTP?
4
+
5
+ Then you need HTTParrot! (or something else that does similar things maybe)
6
+
7
+ = Install it
8
+ gem install httparrot #=> OR include it in the development/test group of your Gemfile
9
+
10
+
11
+ HTTParrot's goal is to simplify building mock HTTP responses and scripting the responses during an HTTP session interaction.
12
+ Responses are mocked in ERB and can be returned via matching params that are setup during use. (We use HTTParrot to
13
+ help mock our HTTP protocol support around the webs)
14
+
15
+ == Example
16
+ TotallyNewAwesomeCompany has decided that too many protocols exist in the Widget space. Unfortunately none of those protocols
17
+ account for how awesome the protocol could be if only TotallyNewAwesomeCompany had a way to force adoption. TNAC has decided
18
+ to implement an HTTP based protocol to exchange Widget data, but they NEED to write tests for the session based HTTP protocol
19
+ (Request => Response => Request => Response)
20
+
21
+ Protocol logic:
22
+ A connection is established with an ConnectWidget document sent to tnac.com/connect_widget
23
+ A valid connection response contains "TNAC: VERIFIED"
24
+ Widget data is requested with a POST sent to tnac.com/widget AND must POST the widget id
25
+
26
+ (we know this isn't very RESTful, but it's a contrived example)
27
+
28
+ Using HTTParrot we can test said protocol in a "simple" way :)
29
+
30
+ describe TNAC::Widget do
31
+
32
+ before(:all) do
33
+ @server = HTTParrot::Server.new
34
+ @server.start
35
+ end
36
+
37
+ after(:all) do
38
+ @server.stop
39
+ end
40
+
41
+ before(:each) do
42
+ HTTParrot::ResponseFactory.clear!
43
+ HTTParrot::ResponseFactory.define(:connection) { |c| c.tnac = "VERIFIED" }
44
+ HTTParrot::ResponseFactory.define(:widget) { |w| w.awesomeness = "YEPYEP" }
45
+ @connection = HTTParrot::ResponseFactory.build(:connection)
46
+ @widget = HTTParrot::ResponseFactory.build(:widget)
47
+ @server.clear!
48
+ end
49
+
50
+ it "totally connects" do
51
+ connection_reg = @server.register(:get, "connect_widget", @connection.to_rack)
52
+ http_connection = Net::HTTP.new("127.0.0.1", HTTParrot::Config.Port)
53
+ http_connection.get("/connect_widget")
54
+ connection_reg.response_count.should eq(1)
55
+ end
56
+
57
+ it "allows me to get a widget" do
58
+ @server.register(:post, /WIDGET/, @widget.to_rack)
59
+ data = "widget=WIDGET&id=1224"
60
+ http_connection = Net::HTTP.new("127.0.0.1", HTTParrot::Config.Port)
61
+ http_connection.post("/widget", data).should match(/VERIFIED/)
62
+ end
63
+
64
+ end
65
+
66
+
67
+ = Matchers
68
+ Matchers are used to register responses based on scripted requests
69
+
70
+ == Regex Matcher
71
+ Matches based on the body of a request matching the Regex passed
72
+
73
+ @server.register(:get, /Match This/, @response.to_rack)
74
+
75
+ == Endpoint Matchers
76
+ Matches based on the endpoint URI that the HTTP verb acts on
77
+
78
+ @server.register(:get, "endpoint", @response.to_rack)
79
+
80
+ == Callable Matcher
81
+ Any parameter that responds to :call can be used to return true/false depending on the body being passed to the callable
82
+ (primarily used with lambdas/procs/method object)
83
+
84
+ @server.register(:get, lambda{ |b| b =~ /awesome/ }, @response.to_rack)
85
+
86
+ == Complex Matcher
87
+ A complex matcher is an Array comprised of any number of the other Matchers
88
+
89
+ @server.register(:get, [/awesome/, lambda{|v| v =~ /awesome/], @response.to_rack)
90
+
91
+
92
+ = Registering Responses
93
+ A response is registered as a HTTP verb type [:get, :post, :head, :delete] followed by the MATCHER and finally a Rack Response
94
+ of the data that will be returned when a match is found. The primary means of the Rack Response in our tests comes from using
95
+ HTTParrot::ResponseFactory to build responses based on ERB templates.
96
+
97
+ (Checkout the tests in the spec folder to build out the templates.....more documentation to come)
data/httparrot.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "httparrot/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "httparrot"
7
+ s.version = HTTParrot::VERSION
8
+ s.authors = ["Brandon Dewitt"]
9
+ s.email = ["brandonsdewitt@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Mock Server for testing HTTP anything!}
12
+ s.description = %q{helps cut through the clutter of HTTP testing}
13
+
14
+ s.rubyforge_project = "httparrot"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ # specify any dependencies here; for example:
22
+ s.add_development_dependency "rspec", "=2.7"
23
+ s.add_development_dependency "rake"
24
+
25
+ s.add_runtime_dependency "rack"
26
+ s.add_runtime_dependency "active_support"
27
+ end
@@ -0,0 +1,89 @@
1
+ require 'httparrot/widget'
2
+
3
+ module HTTParrot
4
+
5
+ class ResponseFactory
6
+
7
+ def self.clear!
8
+ @factory_classes = {}
9
+ end
10
+
11
+ def self.define(factory_class, &block)
12
+ raise error_no_block(factory_class) if block.nil?
13
+ @factory_classes ||= {}
14
+ warn_factory_exists(factory_class) if @factory_classes.keys.include?(factory_class)
15
+ default_object = HTTParrot::Widget.new(:_class => "Widget::#{factory_class.to_s.camelize}")
16
+ @factory_classes[factory_class] = lambda{ block.call(default_object); default_object }
17
+ end
18
+
19
+ def self.build(factory_class, new_options={})
20
+ raise error_for_missing(factory_class) if @factory_classes[factory_class].nil?
21
+ object = @factory_classes[factory_class].call
22
+
23
+ new_options.each do |k, v|
24
+ object.send("#{k}=", v)
25
+ end
26
+
27
+ return Marshal.load(Marshal.dump(object))
28
+ end
29
+
30
+ def self.collection_of(factory_class, number, new_options={})
31
+ collection = []
32
+
33
+ number.times do
34
+ collection << build(factory_class, new_options)
35
+ end
36
+
37
+ return collection
38
+ end
39
+
40
+ def self.one_of(choices)
41
+ warn_no_choices if choices.size <= 0
42
+ choices[rand(choices.size)]
43
+ end
44
+
45
+ private
46
+
47
+ def self.error_for_missing(factory_class)
48
+ "Unknown factory type: #{factory_class} in known factories: #{@factory_classes.keys}"
49
+ end
50
+
51
+ def self.error_no_block(factory_class)
52
+ "No block included in definition of #{factory_class} factory"
53
+ end
54
+
55
+ def self.warn_no_choices
56
+ raise "foo"
57
+ rescue => e
58
+ warn <<-WARNING
59
+ ==============================================================
60
+ No choices were provided for #one_of
61
+
62
+ This constitutes a nil choice
63
+
64
+ At:
65
+
66
+ #{e.backtrace.join("#{$/} ")}
67
+ ==============================================================
68
+ WARNING
69
+ end
70
+
71
+ def self.warn_factory_exists(factory_class)
72
+ raise "foo"
73
+ rescue => e
74
+ warn <<-WARNING
75
+ ==============================================================
76
+ #{factory_class} is already defined as a ResponseFactory
77
+
78
+ This constitutes a redefinition of the factory
79
+
80
+ Redefined at:
81
+
82
+ #{e.backtrace.join("#{$/} ")}
83
+ ==============================================================
84
+ WARNING
85
+ end
86
+
87
+ end
88
+
89
+ end
@@ -0,0 +1,254 @@
1
+ # Mock server to accept requests and return known responses based on request content
2
+ #
3
+ # influenced by: http://dynamicorange.com/2009/02/18/ruby-mock-web-server
4
+ require 'openssl' unless defined?(JRUBY_VERSION)
5
+ require 'ostruct'
6
+ require 'net/http'
7
+ require 'net/https'
8
+ require 'webrick'
9
+ require 'webrick/https'
10
+ require 'rack'
11
+ require 'thread'
12
+
13
+ module HTTParrot
14
+
15
+ class Server
16
+
17
+ attr_accessor :options
18
+
19
+ def initialize(opts={})
20
+ @server_started = @server = nil
21
+ @server_thread = nil
22
+ @secure_started = @secure_server = nil
23
+ @secure_thread = nil
24
+
25
+ @parent_thread = Thread.current
26
+ @options = {
27
+ :Port => 4000,
28
+ :Host => "127.0.0.1"
29
+ }.merge(HTTParrot::Config.config).merge(opts)
30
+
31
+ quiet_options = {
32
+ :Logger => WEBrick::Log::new("/dev/null", 7),
33
+ :AccessLog => []
34
+ }
35
+
36
+ @options.merge!(quiet_options) unless HTTParrot::Config.verbose
37
+
38
+ self.clear!
39
+ end
40
+
41
+ def clear!
42
+ blank_handler = {
43
+ :get => [],
44
+ :post => [],
45
+ :head => [],
46
+ :delete => []
47
+ }
48
+
49
+ # Using Marshal for deep copy purposes
50
+ # slow from an efficiency perspective, but #clear!
51
+ # is not called frequently and this gem is for testing
52
+ @call_handlers = Marshal.load(Marshal.dump(blank_handler))
53
+ @regex_handlers = Marshal.load(Marshal.dump(blank_handler))
54
+ @endpoint_handlers = Marshal.load(Marshal.dump(blank_handler))
55
+ @complex_handlers = Marshal.load(Marshal.dump(blank_handler))
56
+ self
57
+ end
58
+
59
+ def reset_counts
60
+ [@call_handlers, @regex_handlers, @endpoint_handlers, @complex_handlers].each do |handler_type|
61
+ handler_type.each_key do |req_meth|
62
+ handler_type[req_meth].each { |handler| handler.response_count = 0 }
63
+ end
64
+ end
65
+ end
66
+
67
+ def call(env)
68
+ req = Rack::Request.new(env)
69
+ response = respond_with(env)
70
+
71
+ # TODO move this to WEBrick's builtin logging facilities
72
+ if ENV["PARROT_VERBOSE"] || HTTParrot::Config.verbose
73
+ puts "\n>>>>> REQUEST\n"
74
+ puts req.inspect
75
+ #puts req.body.string
76
+
77
+ puts "\n<<<<< RESPONSE\n"
78
+ puts response.inspect
79
+ #response[2].each{ |l| puts l }
80
+ end
81
+
82
+ return response
83
+
84
+ rescue Exception => e
85
+ # reraise the exception in the parent thread if it Errors out
86
+ @parent_thread.raise e
87
+ end
88
+
89
+ def register(http_method, method_key, response, call_with_env = false)
90
+ http_method = http_method.to_s.downcase.to_sym
91
+ raise "http_method in register must be one of [:get, :post, :head, :delete] : #{http_method}" if ![:get, :post, :head, :delete].include?(http_method)
92
+
93
+ response_handler = OpenStruct.new({
94
+ :method_key => method_key,
95
+ :response => response,
96
+ :env? => call_with_env,
97
+ :response_count => 0
98
+ })
99
+
100
+ case
101
+ when method_key.respond_to?(:call) then
102
+ @call_handlers[http_method] << response_handler
103
+ when method_key.is_a?(Regexp) then
104
+ @regex_handlers[http_method] << response_handler
105
+ when method_key.is_a?(String) then
106
+ @endpoint_handlers[http_method] << response_handler
107
+ when method_key.is_a?(Array) then
108
+ @complex_handlers[http_method] << response_handler
109
+ else
110
+ raise "method_key (Handler) must be callable, Regexp, Array, or String"
111
+ end
112
+
113
+ return response_handler
114
+ end
115
+
116
+ def start(startup_interval = 1)
117
+ start_server
118
+ start_secure_server if options[:ssl]
119
+ sleep startup_interval # Ensure the server has time to startup
120
+ sleep startup_interval if !running? # Give it a little more time if they didn't start
121
+ end
122
+
123
+ def running?
124
+ secure_run_running = options[:ssl] ? (!@secure_server.nil? && @secure_started) : true
125
+ @server_started && !@server.nil? && secure_run_running
126
+ end
127
+ alias_method :started?, :running?
128
+
129
+ def stop(shutdown_interval = 0)
130
+ @server.shutdown if @server.respond_to?(:shutdown)
131
+ @secure_server.shutdown if @secure_server.respond_to?(:shutdown)
132
+ sleep shutdown_interval
133
+ Thread.kill(@server_thread) if !@server_thread.nil?
134
+ Thread.kill(@secure_thread) if !@secure_thread.nil?
135
+ @server_started = @server = nil
136
+ @secure_started = @secure_server = nil
137
+ end
138
+
139
+ private
140
+
141
+ # Increment the number of times a handler was used (for asserting usage)
142
+ def increment_return(handler)
143
+ handler.response_count = handler.response_count + 1
144
+ return handler.response
145
+ end
146
+
147
+ def call_handler_match?(call_handler, env, request, request_method, request_body)
148
+ call_arg = (call_handler.env? ? env : request_body)
149
+
150
+ return call_handler.method_key.call(call_arg)
151
+ end
152
+
153
+ def complex_handler_match?(complex_handler, env, request, request_method, request_body)
154
+
155
+ complex_handler.method_key.inject(true) do |matching, handler|
156
+ current_handler_match = false
157
+
158
+ if matching
159
+ current_handler = OpenStruct.new({
160
+ :env? => complex_handler.env?,
161
+ :method_key => handler
162
+ })
163
+
164
+ case
165
+ when handler.respond_to?(:call) then
166
+ current_handler_match = call_handler_match?(current_handler, env, request, request_method, request_body)
167
+ when handler.is_a?(Regexp) then
168
+ current_handler_match = regex_handler_match?(current_handler, env, request, request_method, request_body)
169
+ when handler.is_a?(String) then
170
+ current_handler_match = endpoint_handler_match?(current_handler, env, request, request_method, request_body)
171
+ end
172
+ end
173
+
174
+ matching && current_handler_match
175
+ end
176
+ end
177
+
178
+ def endpoint_handler_match?(endpoint_handler, env, request, request_method, request_body)
179
+ return request.path_info =~ /#{endpoint_handler.method_key}/i
180
+ end
181
+
182
+ def regex_handler_match?(regex_handler, env, request, request_method, request_body)
183
+ return regex_handler.method_key =~ request_body
184
+ end
185
+
186
+ def respond_with(env)
187
+ req = Rack::Request.new(env)
188
+ req_meth = req.request_method.downcase.to_sym
189
+ request_body = req.body.string
190
+
191
+ @complex_handlers[req_meth].each do |com_handler|
192
+ return increment_return(com_handler) if complex_handler_match?(com_handler, env, req, req_meth, request_body)
193
+ end
194
+
195
+ @call_handlers[req_meth].each do |call_handler|
196
+ return increment_return(call_handler) if call_handler_match?(call_handler, env, req, req_meth, request_body)
197
+ end
198
+
199
+ @regex_handlers[req_meth].each do |reg_handler|
200
+ return increment_return(reg_handler) if regex_handler_match?(reg_handler, env, req, req_meth, request_body)
201
+ end
202
+
203
+ @endpoint_handlers[req_meth].each do |end_handler|
204
+ return increment_return(end_handler) if endpoint_handler_match?(end_handler, env, req, req_meth, request_body)
205
+ end
206
+
207
+ return no_mock_error(req_meth)
208
+ end
209
+
210
+ def no_mock_error(request_method)
211
+ error_message = "No matched request handlers for: #{request_method}"
212
+ [404, { "Content-Type" => "text/plain", "Content-Length" => error_message.size.to_s }, [error_message]]
213
+ end
214
+
215
+ def start_server
216
+ if @server_thread.nil? || !@server_thread.alive?
217
+ @server_thread = Thread.new(self, @options) do |server, options|
218
+ Rack::Handler::WEBrick.run(server, options) { |s| @server = s }
219
+ end
220
+
221
+ trap(:INT){ @server_thread.terminate; @server_thread = nil }
222
+ @server_started = true
223
+ end
224
+ end
225
+
226
+ def start_secure_server
227
+ if @secure_thread.nil? || !@secure_thread.alive?
228
+ @secure_thread = Thread.new(self, @options) do |server, options|
229
+ options = options.merge(:Port => options[:SSLPort],
230
+ :SSLEnable => true,
231
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
232
+ :SSLCertificate => ssl_cert,
233
+ :SSLPrivateKey => ssl_key,
234
+ :SSLCertName => [[ "CN", "127.0.0.1" ]])
235
+
236
+ Rack::Handler::WEBrick.run(server, options) { |s| @secure_server = s }
237
+ end
238
+
239
+ trap(:INT){ @secure_thread.terminate; @secure_thread = nil }
240
+ @secure_started = true
241
+ end
242
+ end
243
+
244
+ def ssl_key
245
+ OpenSSL::PKey::RSA.new(File.read(File.dirname(__FILE__) + "/ssl/" + "server.key"), "httparrot")
246
+ end
247
+
248
+ def ssl_cert
249
+ OpenSSL::X509::Certificate.new(File.read(File.dirname(__FILE__) + "/ssl/" + "server.crt"))
250
+ end
251
+
252
+ end
253
+
254
+ end
@@ -0,0 +1,14 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIICEzCCAXwCCQCqQYI2AsyUpDANBgkqhkiG9w0BAQUFADBOMQswCQYDVQQGEwJV
3
+ UzETMBEGA1UECBMKU29tZS1TdGF0ZTEUMBIGA1UEBxMLU3ByaW5nZmllbGQxFDAS
4
+ BgNVBAoTC0F3ZXNvbWV0b3duMB4XDTExMTIyMTAwMjQwMVoXDTIxMTIxODAwMjQw
5
+ MVowTjELMAkGA1UEBhMCVVMxEzARBgNVBAgTClNvbWUtU3RhdGUxFDASBgNVBAcT
6
+ C1NwcmluZ2ZpZWxkMRQwEgYDVQQKEwtBd2Vzb21ldG93bjCBnzANBgkqhkiG9w0B
7
+ AQEFAAOBjQAwgYkCgYEA1sbHVwOzZqIKFKkd8bBaGma9qXYrSrf8bGj9SZREIpAk
8
+ kZGH2Dyta27P7njUGcB3RecTogQHAS/YVkPuSfPHlbU2qVDBmqT1ZVIComW0A7iI
9
+ HiSsevGMgJAPNG2ekLBKJexxoS0nqfHO6G8nvPXIjWsXwT4IIL59sRxOx96Fc+0C
10
+ AwEAATANBgkqhkiG9w0BAQUFAAOBgQCaDVQErv6g7SU7f5NJ3F+CHksXEkgAGrYd
11
+ xb9mnaJlnJX8G/iG2jsE5lsNttQWBsLRrZ1x6eaVBOMlYnHTOny56TexLQqqFvSW
12
+ HiJeG8HcrVdW3u9aCjxLojcWVlnf+doIwbS87Q6qGu6N7cAof3UmI9vMwuwnNjty
13
+ 9lXwRcxjMA==
14
+ -----END CERTIFICATE-----
@@ -0,0 +1,15 @@
1
+ -----BEGIN RSA PRIVATE KEY-----
2
+ MIICXgIBAAKBgQDWxsdXA7NmogoUqR3xsFoaZr2pditKt/xsaP1JlEQikCSRkYfY
3
+ PK1rbs/ueNQZwHdF5xOiBAcBL9hWQ+5J88eVtTapUMGapPVlUgKiZbQDuIgeJKx6
4
+ 8YyAkA80bZ6QsEol7HGhLSep8c7obye89ciNaxfBPgggvn2xHE7H3oVz7QIDAQAB
5
+ AoGBAMUvQMqhqi7bLBgl4EkKGN9OXmjcBgkWfBjoF0tbZWa6IejHzQl5Q9pzpVGS
6
+ +2AdNSQnb/36ZpfvXlZtDbQ1rZEUzbMaPy6e2YV90B95xLNgVlSa4tOcoZ70Lpfu
7
+ 2avWv5RaOHtySyV76JMdpiREJaNhDLEfkDBNRcuoI5Xu1RohAkEA+AaKy1YU4z2Z
8
+ KNaNi1rzXAxvdiC+RsKuOHXyyoy75wbwD4TB3pQuyFoXlvzsvw6ijmTUHxLA4aGv
9
+ tLKPu2JRhQJBAN2ukZxOAzf57B3HoI5Oti7fyoCUpxXfnJSypMK9KyihJRVX0/Ko
10
+ 3+VEKdSEABcqWF2/ViAbHkq3AuX9g9sm8UkCQEQF108JHtVr8XOH1G4h1ZirOG6X
11
+ cFgL0KhfgOUYT/h+qJw49srKrUH5o3qfh3am1uJiuOKEzC2VoJDYYB8uSdkCQQDL
12
+ xlN9XczhoLAmM8Hn7nzTm83W4k6w8atKmOiRRjitEWw4MVLYJdoiMsVM38YBhWBT
13
+ VLXDr4np3k8gwSh6xFJZAkEAqPQIaRNZEI/uVoxAOhgkzZ8ci9YjRP1HmVdYy/Sv
14
+ nSrcq5XcGzIwYQmrUDMnp9k3pL0Lm611fAhgicgbq9a0ew==
15
+ -----END RSA PRIVATE KEY-----
@@ -0,0 +1,3 @@
1
+ module HTTParrot
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,113 @@
1
+ require 'erb'
2
+ require 'ostruct'
3
+ require 'active_support/core_ext/numeric'
4
+ require 'active_support/core_ext/time/conversions'
5
+ require 'active_support/core_ext/string/inflections'
6
+
7
+ module HTTParrot
8
+
9
+ class Widget < OpenStruct
10
+
11
+ def initialize(defaults = {})
12
+ @parent_overwrite = false
13
+ @headers = {}
14
+ super(defaults)
15
+ end
16
+
17
+ def header
18
+ @headers
19
+ end
20
+
21
+ def parent(parental_class, defaults={})
22
+ case
23
+ when parental_class.is_a?(Symbol) then
24
+ parental_class = HTTParrot::ResponseFactory.build(parental_class, defaults)
25
+ when !parental_class.respond_to?(:to_hash) then
26
+ raise "Parent must be a symbol or respond_to #to_hash"
27
+ end
28
+
29
+ merge_parent(parental_class)
30
+ end
31
+
32
+ def parent!(parental_class, defaults = {})
33
+ @parent_overwrite = true
34
+ parent(parental_class, defaults)
35
+ @parent_overwrite = false
36
+ end
37
+
38
+ def rack_response(response_code = 200)
39
+ rendered_response = self.to_s
40
+ return [response_code, @headers.merge({"Content-Length" => rendered_response.size.to_s}), [rendered_response]]
41
+ end
42
+ alias_method :to_rack, :rack_response
43
+ alias_method :to_rack_response, :rack_response
44
+
45
+ def to_hash
46
+ self.marshal_dump
47
+ end
48
+
49
+ def to_s
50
+ set_template_file
51
+
52
+ if @table.has_key?(:template_file) && !self.template_file.nil?
53
+ @headers = {}
54
+ file_string = File.read(self.template_file)
55
+ current_template = ERB.new(file_string, nil, "<>")
56
+ return current_template.result(binding)
57
+ else
58
+ warn_no_template
59
+ return self.inspect
60
+ end
61
+ end
62
+
63
+ private
64
+
65
+ def merge_parent(parental_class)
66
+ case
67
+ when @parent_overwrite then
68
+ @table.merge!(parental_class.to_hash)
69
+ else
70
+ @table.merge!(parental_class.to_hash.merge(@table))
71
+ end
72
+ end
73
+
74
+ def template_root_search
75
+ self._class ||= self.class.to_s
76
+
77
+ template_root = HTTParrot::Config.config[:template_root] || ".."
78
+ template_root = template_root[0..-1] if template_root[-1] == "/"
79
+ filename = self._class.gsub("Widget::", "")
80
+ filename.gsub!("HTTParrot::", "")
81
+ return "#{template_root}/**/#{filename.underscore}.erb"
82
+ end
83
+
84
+ def set_template_file
85
+ if self.template_file.nil? || self.template_file.empty?
86
+ self.template_file = Dir.glob(template_root_search).first
87
+ end
88
+ end
89
+
90
+ def warn_no_template
91
+ raise "foo"
92
+ rescue => e
93
+ warn <<-WARNING
94
+ ===================================================================
95
+ #{self.class} does not have a template_file associated
96
+
97
+ This will leave the response as an inspection of the class
98
+ and is probably not the intended behavior. Before returning
99
+ a rack_response, define a template file to render with.
100
+
101
+ template_root search:
102
+ #{template_root_search}
103
+
104
+ Called at:
105
+
106
+ #{e.backtrace.join("#{$/} ")}
107
+ ===================================================================
108
+ WARNING
109
+ end
110
+
111
+ end
112
+
113
+ end
data/lib/httparrot.rb ADDED
@@ -0,0 +1,51 @@
1
+ require "httparrot/version"
2
+ require "httparrot/server"
3
+ require "httparrot/response_factory"
4
+
5
+ require "ostruct"
6
+
7
+ module HTTParrot
8
+ class Config
9
+ @config = OpenStruct.new
10
+
11
+ def self.configure
12
+ yield self
13
+ end
14
+
15
+ def self.config
16
+ @config.instance_variable_get(:@table)
17
+ end
18
+
19
+ def self.valid_key?(key)
20
+ [ :Port,
21
+ :SSLPort,
22
+ :ssl,
23
+ :template_root,
24
+ :verbose ].include?(key)
25
+ end
26
+
27
+ def self.restore_defaults!
28
+ self.configure do |c|
29
+ c.Port = 4000
30
+ c.SSLPort = c.Port + 1
31
+ c.ssl = true
32
+ c.verbose = false
33
+ c.template_root = nil
34
+ end
35
+ end
36
+
37
+ def self.method_missing(sym, *args, &blk)
38
+ case
39
+ when sym.to_s =~ /(.+)=$/ && valid_key?($1.to_sym) then
40
+ @config.send(sym, *args, &blk)
41
+ when @config.respond_to?(sym) then
42
+ @config.send(sym, *args, &blk)
43
+ else
44
+ super
45
+ end
46
+ end
47
+
48
+ end
49
+ end
50
+
51
+ HTTParrot::Config.restore_defaults!