httparrot 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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!