grifter 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +12 -0
- data/LICENSE.txt +20 -0
- data/Rakefile +41 -0
- data/Readme.md +119 -0
- data/VERSION +1 -0
- data/bin/grift +62 -0
- data/example/grifter.yml +7 -0
- data/example/owm_grifts/weather_grifts.rb +3 -0
- data/example/temperatures.rb +12 -0
- data/grifter.gemspec +80 -0
- data/lib/grifter.rb +77 -0
- data/lib/grifter/blankslate.rb +126 -0
- data/lib/grifter/configuration.rb +89 -0
- data/lib/grifter/helpers.rb +24 -0
- data/lib/grifter/http_service.rb +142 -0
- data/lib/grifter/json_helpers.rb +38 -0
- data/lib/grifter/log.rb +35 -0
- data/spec/configuration_spec.rb +127 -0
- data/spec/grifter_spec.rb +52 -0
- data/spec/http_service_spec.rb +81 -0
- data/spec/json_helpers_spec.rb +53 -0
- data/spec/resources/example_config.yml +19 -0
- data/spec/resources/grifter.yml +11 -0
- data/spec/resources/syntax_error_grifts/eval_error_grifts.rb +3 -0
- data/spec/resources/twitter_grifts/oauth_grifts.rb +23 -0
- data/spec/resources/twitter_grifts/timeline_grifts.rb +9 -0
- data/spec/rspec_helper_spec.rb +30 -0
- data/spec/support/require_all_lib_files.rb +0 -0
- data/spec/support/spec_helper.rb +19 -0
- metadata +159 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Robert Schultheis (Knewton)
|
4
|
+
# This is a slightly modified BlankSlate, as taken from https://github.com/masover/blankslate
|
5
|
+
# Included is some stuff allowing us to send methods back to a parent
|
6
|
+
# Which makes our api script DSL very easy to use
|
7
|
+
#
|
8
|
+
# Original file comments:
|
9
|
+
#
|
10
|
+
#--
|
11
|
+
# Copyright 2004, 2006 by Jim Weirich (jim@weirichhouse.org).
|
12
|
+
# All rights reserved.
|
13
|
+
|
14
|
+
# Permission is granted for use, copying, modification, distribution,
|
15
|
+
# and distribution of modified versions of this work as long as the
|
16
|
+
# above copyright notice is included.
|
17
|
+
#++
|
18
|
+
|
19
|
+
######################################################################
|
20
|
+
# BlankSlate provides an abstract base class with no predefined
|
21
|
+
# methods (except for <tt>\_\_send__</tt> and <tt>\_\_id__</tt>).
|
22
|
+
# BlankSlate is useful as a base class when writing classes that
|
23
|
+
# depend upon <tt>method_missing</tt> (e.g. dynamic proxies).
|
24
|
+
#
|
25
|
+
class BlankSlate
|
26
|
+
class << self
|
27
|
+
|
28
|
+
# Hide the method named +name+ in the BlankSlate class. Don't
|
29
|
+
# hide +instance_eval+ or any method beginning with "__".
|
30
|
+
def hide(name)
|
31
|
+
methods = instance_methods.map(&:to_sym)
|
32
|
+
if methods.include?(name.to_sym) and
|
33
|
+
name !~ /^(__|instance_eval)/
|
34
|
+
@hidden_methods ||= {}
|
35
|
+
@hidden_methods[name.to_sym] = instance_method(name)
|
36
|
+
undef_method name
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_hidden_method(name)
|
41
|
+
@hidden_methods ||= {}
|
42
|
+
@hidden_methods[name] || superclass.find_hidden_method(name)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Redefine a previously hidden method so that it may be called on a blank
|
46
|
+
# slate object.
|
47
|
+
def reveal(name)
|
48
|
+
hidden_method = find_hidden_method(name)
|
49
|
+
fail "Don't know how to reveal method '#{name}'" unless hidden_method
|
50
|
+
define_method(name, hidden_method)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
(instance_methods - [:object_id]).each { |m| hide(m) }
|
55
|
+
|
56
|
+
def initialize(parent)
|
57
|
+
@parent = parent
|
58
|
+
end
|
59
|
+
|
60
|
+
def method_missing(method_id, *arguments, &block)
|
61
|
+
@parent.send(method_id, *arguments, &block)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
######################################################################
|
66
|
+
# Since Ruby is very dynamic, methods added to the ancestors of
|
67
|
+
# BlankSlate <em>after BlankSlate is defined</em> will show up in the
|
68
|
+
# list of available BlankSlate methods. We handle this by defining a
|
69
|
+
# hook in the Object and Kernel classes that will hide any method
|
70
|
+
# defined after BlankSlate has been loaded.
|
71
|
+
#
|
72
|
+
module Kernel
|
73
|
+
class << self
|
74
|
+
alias_method :blank_slate_method_added, :method_added
|
75
|
+
|
76
|
+
# Detect method additions to Kernel and remove them in the
|
77
|
+
# BlankSlate class.
|
78
|
+
def method_added(name)
|
79
|
+
result = blank_slate_method_added(name)
|
80
|
+
return result if self != Kernel
|
81
|
+
BlankSlate.hide(name)
|
82
|
+
result
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
######################################################################
|
88
|
+
# Same as above, except in Object.
|
89
|
+
#
|
90
|
+
class Object
|
91
|
+
class << self
|
92
|
+
alias_method :blank_slate_method_added, :method_added
|
93
|
+
|
94
|
+
# Detect method additions to Object and remove them in the
|
95
|
+
# BlankSlate class.
|
96
|
+
def method_added(name)
|
97
|
+
result = blank_slate_method_added(name)
|
98
|
+
return result if self != Object
|
99
|
+
BlankSlate.hide(name)
|
100
|
+
result
|
101
|
+
end
|
102
|
+
|
103
|
+
def find_hidden_method(name)
|
104
|
+
nil
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
######################################################################
|
110
|
+
# Also, modules included into Object need to be scanned and have their
|
111
|
+
# instance methods removed from blank slate. In theory, modules
|
112
|
+
# included into Kernel would have to be removed as well, but a
|
113
|
+
# "feature" of Ruby prevents late includes into modules from being
|
114
|
+
# exposed in the first place.
|
115
|
+
#
|
116
|
+
class Module
|
117
|
+
alias blankslate_original_append_features append_features
|
118
|
+
def append_features(mod)
|
119
|
+
result = blankslate_original_append_features(mod)
|
120
|
+
return result if mod != Object
|
121
|
+
instance_methods.each do |name|
|
122
|
+
BlankSlate.hide(name)
|
123
|
+
end
|
124
|
+
result
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
require_relative 'log'
|
4
|
+
|
5
|
+
class Grifter
|
6
|
+
module Configuration
|
7
|
+
def recursive_symbolize hash
|
8
|
+
hash.inject({}) do |h, (k,v)|
|
9
|
+
h[k.intern] = case v
|
10
|
+
when Hash
|
11
|
+
recursive_symbolize v
|
12
|
+
else
|
13
|
+
v
|
14
|
+
end
|
15
|
+
h
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def load_config_file options={}
|
20
|
+
options = {
|
21
|
+
config_file: ENV['GRIFTER_CONFIG_FILE'] ? ENV['GRIFTER_CONFIG_FILE'] : 'grifter.yml',
|
22
|
+
environment: ENV['GRIFTER_ENVIRONMENT'],
|
23
|
+
}.merge(options)
|
24
|
+
Log.debug "Loading config file '#{options[:config_file]}'"
|
25
|
+
unless File.exist?(options[:config_file])
|
26
|
+
raise "No such config file: '#{options[:config_file]}'"
|
27
|
+
end
|
28
|
+
hash = YAML.load_file(options[:config_file])
|
29
|
+
symbolized = recursive_symbolize(hash)
|
30
|
+
normalize_config(symbolized, options)
|
31
|
+
end
|
32
|
+
|
33
|
+
#this method ensure the config hash has everything we need to instantiate the service objects
|
34
|
+
def normalize_config config, options={}
|
35
|
+
unless ((config.is_a? Hash) &&
|
36
|
+
(config.has_key? :services) &&
|
37
|
+
(config[:services].is_a? Hash) &&
|
38
|
+
(config[:services].length > 0))
|
39
|
+
raise GrifterConfigurationError.new ":services block not found in configuration"
|
40
|
+
end
|
41
|
+
|
42
|
+
#fill out services block entirely for each service
|
43
|
+
config[:services].each_pair do |service_name, service_config|
|
44
|
+
service_config[:name] = service_name.to_s
|
45
|
+
#setup port config
|
46
|
+
unless service_config[:port]
|
47
|
+
if service_config[:ssl]
|
48
|
+
service_config[:port] = 443
|
49
|
+
else
|
50
|
+
service_config[:port] = 80
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
#ssl config
|
55
|
+
unless service_config[:ssl]
|
56
|
+
service_config[:ssl] = false
|
57
|
+
end
|
58
|
+
|
59
|
+
#ignore_ssl_certificate
|
60
|
+
unless service_config[:ignore_ssl_cert]
|
61
|
+
service_config[:ignore_ssl_cert] = false
|
62
|
+
end
|
63
|
+
|
64
|
+
unless service_config[:base_uri]
|
65
|
+
service_config[:base_uri] = ''
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
#merge any environment overrides into the service block
|
71
|
+
if options[:environment]
|
72
|
+
options[:environment] = options[:environment].to_sym
|
73
|
+
unless config[:environments] && config[:environments][options[:environment]]
|
74
|
+
raise GrifterConfigurationError.new "No such environment specified in config: '#{options[:environment]}'"
|
75
|
+
end
|
76
|
+
|
77
|
+
config[:environments][options[:environment]].each_pair do |service_name, service_overrides|
|
78
|
+
config[:services][service_name].merge! service_overrides
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
return config
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class GrifterConfigurationError < Exception
|
89
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative '../grifter'
|
2
|
+
|
3
|
+
class Grifter
|
4
|
+
module Helpers
|
5
|
+
|
6
|
+
def grifter_instance
|
7
|
+
@@grifter_instance ||= ::Grifter.new
|
8
|
+
end
|
9
|
+
module_function :grifter_instance
|
10
|
+
|
11
|
+
def self.included(mod)
|
12
|
+
def grifter
|
13
|
+
grifter_instance
|
14
|
+
end
|
15
|
+
|
16
|
+
grifter_instance.singleton_methods.each do |meth|
|
17
|
+
define_method meth do |*args, &block|
|
18
|
+
grifter_instance.send(meth, *args, &block)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
require_relative 'json_helpers'
|
4
|
+
require_relative 'log'
|
5
|
+
|
6
|
+
class Grifter
|
7
|
+
class HTTPService
|
8
|
+
|
9
|
+
include Grifter::JsonHelpers
|
10
|
+
|
11
|
+
def initialize config
|
12
|
+
|
13
|
+
@config = config
|
14
|
+
@name = config[:name]
|
15
|
+
@base_uri = config[:base_uri]
|
16
|
+
|
17
|
+
Log.debug "Configuring service '#{@name}' with:\n\t#{@config.inspect}"
|
18
|
+
|
19
|
+
@http = Net::HTTP.new(@config[:hostname], @config[:port])
|
20
|
+
@http.use_ssl = @config[:ssl]
|
21
|
+
@http.verify_mode = OpenSSL::SSL::VERIFY_NONE if @config[:ignore_ssl_cert]
|
22
|
+
|
23
|
+
@headers = {
|
24
|
+
'accept' => 'application/json',
|
25
|
+
'content-type' => 'application/json',
|
26
|
+
}
|
27
|
+
if @config[:default_headers]
|
28
|
+
Log.debug "Default headers configured: " + @config[:default_headers].inspect
|
29
|
+
@config[:default_headers].each_pair do |k, v|
|
30
|
+
@headers[k.to_s] = v.to_s
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
#allow stubbing http if we are testing
|
36
|
+
attr_reader :http if defined?(RSpec)
|
37
|
+
|
38
|
+
attr_reader :headers, :name, :config
|
39
|
+
|
40
|
+
#this is useful for testing apis, and other times
|
41
|
+
#you want to interrogate the http details of a response
|
42
|
+
attr_reader :last_request, :last_response
|
43
|
+
|
44
|
+
RequestLogSeperator = '-'*40
|
45
|
+
def do_request req
|
46
|
+
Log.debug RequestLogSeperator
|
47
|
+
Log.debug "#{req.class} #{req.path}"
|
48
|
+
Log.debug "HEADERS: #{req.to_hash}"
|
49
|
+
Log.debug "BODY:\n#{req.body}" if req.request_body_permitted?
|
50
|
+
response = @http.request(req)
|
51
|
+
Log.debug "RESPONSE CODE: #{response.code}"
|
52
|
+
Log.debug "RESPONSE HEADERS: #{response.to_hash}"
|
53
|
+
Log.debug "RESPONSE BODY:\n#{jsonify response.body}\n"
|
54
|
+
|
55
|
+
@last_request = req
|
56
|
+
@last_response = response
|
57
|
+
|
58
|
+
raise RequestException.new(req, response) unless response.kind_of? Net::HTTPSuccess
|
59
|
+
|
60
|
+
objectify response.body
|
61
|
+
end
|
62
|
+
|
63
|
+
#add base uri to request
|
64
|
+
def make_path path_suffix, base_uri=nil
|
65
|
+
base_uri_to_use = base_uri ? base_uri : @base_uri
|
66
|
+
if base_uri_to_use
|
67
|
+
base_uri_to_use + path_suffix
|
68
|
+
else
|
69
|
+
path_suffix
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def make_headers options
|
74
|
+
if options[:additional_headers]
|
75
|
+
@headers.merge options[:additional_headers]
|
76
|
+
elsif options[:headers]
|
77
|
+
options[:headers]
|
78
|
+
else
|
79
|
+
@headers
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def req_args path, options
|
84
|
+
[make_path(path, options[:base_uri]), make_headers(options)]
|
85
|
+
end
|
86
|
+
|
87
|
+
def get path, options={}
|
88
|
+
req = Net::HTTP::Get.new(*req_args(path, options))
|
89
|
+
do_request req
|
90
|
+
end
|
91
|
+
|
92
|
+
def head path, options={}
|
93
|
+
req = Net::HTTP::Head.new(*req_args(path, options))
|
94
|
+
do_request req
|
95
|
+
end
|
96
|
+
|
97
|
+
def delete path, options={}
|
98
|
+
req = Net::HTTP::Delete.new(*req_args(path, options))
|
99
|
+
do_request req
|
100
|
+
end
|
101
|
+
|
102
|
+
def post path, obj, options={}
|
103
|
+
req = Net::HTTP::Post.new(*req_args(path, options))
|
104
|
+
req.body = jsonify(obj)
|
105
|
+
do_request req
|
106
|
+
end
|
107
|
+
|
108
|
+
def put path, obj, options={}
|
109
|
+
req = Net::HTTP::Put.new(*req_args(path, options))
|
110
|
+
req.body = jsonify(obj)
|
111
|
+
do_request req
|
112
|
+
end
|
113
|
+
|
114
|
+
def post_form path, params, options={}
|
115
|
+
request_obj = Net::HTTP::Post.new(*req_args(path, options))
|
116
|
+
request_obj.set_form_data params
|
117
|
+
request_obj.basic_auth(options[:username], options[:password]) if options[:username]
|
118
|
+
do_request request_obj
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
class RequestException < Exception
|
123
|
+
def initialize(request, response)
|
124
|
+
@request, @response = request, response
|
125
|
+
end
|
126
|
+
|
127
|
+
#this makes good info show up in rspec reports
|
128
|
+
def to_s
|
129
|
+
"#{self.class}\nResponseCode: #{@response.code}\nResponseBody:\n#{@response.body}"
|
130
|
+
end
|
131
|
+
|
132
|
+
#shortcut methods
|
133
|
+
def code
|
134
|
+
@response.code
|
135
|
+
end
|
136
|
+
|
137
|
+
def body
|
138
|
+
@response.body
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
class Grifter
|
4
|
+
module JsonHelpers
|
5
|
+
|
6
|
+
#always returns a string, intended for request bodies
|
7
|
+
#every attempt is made to ensure string is valid json
|
8
|
+
#but if that is not possible, then its returned as is
|
9
|
+
def jsonify obj
|
10
|
+
case obj
|
11
|
+
when String
|
12
|
+
JSON.pretty_generate(JSON.parse(obj))
|
13
|
+
when Hash, Array
|
14
|
+
JSON.pretty_generate(obj)
|
15
|
+
else
|
16
|
+
obj.to_s
|
17
|
+
end
|
18
|
+
rescue Exception
|
19
|
+
obj.to_s
|
20
|
+
end
|
21
|
+
#module_function :jsonify
|
22
|
+
|
23
|
+
#attempts to parse json strings into native ruby objects
|
24
|
+
def objectify json_string
|
25
|
+
case json_string
|
26
|
+
when Hash, Array
|
27
|
+
return json_string
|
28
|
+
else
|
29
|
+
JSON.parse(json_string.to_s)
|
30
|
+
end
|
31
|
+
rescue Exception => e
|
32
|
+
Log.debug "Unable to parse non-json object: #{e.to_s}"
|
33
|
+
json_string
|
34
|
+
end
|
35
|
+
#module_function :objectify
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
data/lib/grifter/log.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'logger'
|
2
|
+
|
3
|
+
class Grifter
|
4
|
+
class Log
|
5
|
+
GrifterFormatter = proc do |severity, datetime, progname, msg|
|
6
|
+
"#{severity[0]}: [#{datetime.strftime('%m/%d/%y %H:%M:%S')}][#{progname}] - #{msg}\n"
|
7
|
+
end
|
8
|
+
|
9
|
+
@@loggers = []
|
10
|
+
def self.add_logger handle
|
11
|
+
new_logger = Logger.new handle
|
12
|
+
new_logger.progname = 'grifter'
|
13
|
+
new_logger.formatter = GrifterFormatter
|
14
|
+
@@loggers << new_logger
|
15
|
+
end
|
16
|
+
|
17
|
+
self.add_logger(STDOUT)
|
18
|
+
|
19
|
+
def self.level= log_level
|
20
|
+
@@loggers.each { |logger| logger.level = log_level}
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.log level, msg
|
24
|
+
@@loggers.each {|logger| logger.send(level, msg)}
|
25
|
+
end
|
26
|
+
|
27
|
+
[:fatal, :error, :warn, :info,:debug].each do |log_method|
|
28
|
+
define_singleton_method log_method do |msg|
|
29
|
+
log(log_method, msg)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
|