api_hammer 0.0.3 → 0.1.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/bin/hc +220 -0
- data/lib/api_hammer/public_instance_exec.rb +31 -0
- data/lib/api_hammer/version.rb +1 -1
- data/test/public_instance_exec_test.rb +43 -0
- metadata +22 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 30f42f8b22545e005c29603af5d98d6874e8027b
|
4
|
+
data.tar.gz: 7257d0076928ecc3c328596243c3b16d465dba6b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a3ea4254d06322b584a4b78ec25d3334280c5118406913500cfaf8d2fbc84a9c5478fd6c0d2bfdb15ead42f795b58a9abc61bfa7daff88696195df93cd21f2d3
|
7
|
+
data.tar.gz: a275cccb8fba41c50c2781bc2423477bec6a688b0d831145ee044889193ab9d70105bf17a138d88cf3380ee4fbbd2216ad1ea42265609bef407729e90680468d
|
data/CHANGELOG.md
CHANGED
data/bin/hc
ADDED
@@ -0,0 +1,220 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'faraday'
|
5
|
+
require 'logger'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
# OPTION PARSER
|
9
|
+
|
10
|
+
require 'optparse'
|
11
|
+
|
12
|
+
# $options default values
|
13
|
+
$options = {
|
14
|
+
:verbose => true,
|
15
|
+
:color => nil,
|
16
|
+
:no_ssl_verify => false,
|
17
|
+
:headers => {},
|
18
|
+
}
|
19
|
+
|
20
|
+
$oauth = {}
|
21
|
+
|
22
|
+
opt_parser = OptionParser.new do |opts|
|
23
|
+
opts.banner = "Usage: #{$0} [options] <verb> <url> [body]"
|
24
|
+
|
25
|
+
opts.on("-v", "--[no-]verbose", "Run verbosely - output is like curl -v (this is the default)") do |v|
|
26
|
+
$options[:verbose] = v
|
27
|
+
end
|
28
|
+
opts.on("-q", "Run quietly - only outputs the response body (same as --no-verbose)") do |v|
|
29
|
+
$options[:verbose] = !v
|
30
|
+
end
|
31
|
+
opts.on("--[no-]color", "Color the output (defaults to color if the output device is a TTY)") do |v|
|
32
|
+
$options[:color] = v
|
33
|
+
end
|
34
|
+
opts.on("-t", "--content-type CONTENT-TYPE", "Sets the Content-Type header of the request") do |v|
|
35
|
+
$options[:headers]['Content-Type'.downcase] = v
|
36
|
+
end
|
37
|
+
opts.on("--oauth-token TOKEN") do |token|
|
38
|
+
$oauth[:token] = token
|
39
|
+
end
|
40
|
+
opts.on("--oauth-token-secret TOKEN_SECRET") do |token_secret|
|
41
|
+
$oauth[:token_secret] = token_secret
|
42
|
+
end
|
43
|
+
opts.on("--oauth-consumer-key CONSUMER_KEY") do |consumer_key|
|
44
|
+
$oauth[:consumer_key] = consumer_key
|
45
|
+
end
|
46
|
+
opts.on("--oauth-consumer-secret CONSUMER_SECRET") do |consumer_secret|
|
47
|
+
$oauth[:consumer_secret] = consumer_secret
|
48
|
+
end
|
49
|
+
opts.on("--oauth-signature-method SIGNATURE_METHOD") do |signature_method|
|
50
|
+
$oauth[:signature_method] = signature_method
|
51
|
+
end
|
52
|
+
|
53
|
+
opts.on("--no-ssl-verify", "Disables SSL verification - use cautiously!") do
|
54
|
+
$options[:no_ssl_verify] = true
|
55
|
+
end
|
56
|
+
opts.on("-H", "--header HEADER", "Set a header") do |header|
|
57
|
+
if header =~ /\A([^:]+):\s*(.*)\z/m # this could be more strictly conformant to rfc, but whatever
|
58
|
+
field_name = $1
|
59
|
+
field_value = $2
|
60
|
+
$options[:headers][field_name.downcase] = field_value
|
61
|
+
else
|
62
|
+
abort "bad header value given: #{header}"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
opt_parser.parse!
|
67
|
+
abort(opt_parser.help) unless (2..3).include?(ARGV.size)
|
68
|
+
|
69
|
+
# OUTPUTTERS FOR FARADAY THAT SHOULD MOVE TO A LIB SOMEWHERE
|
70
|
+
|
71
|
+
# outputs the response body to the given output device (defaulting to STDOUT)
|
72
|
+
class FaradayOutputter < Faraday::Middleware
|
73
|
+
def initialize(app, outdev=STDOUT)
|
74
|
+
@app=app
|
75
|
+
@outdev = outdev
|
76
|
+
end
|
77
|
+
|
78
|
+
def call(request_env)
|
79
|
+
@app.call(request_env).on_complete do |response_env|
|
80
|
+
@outdev.puts(response_env[:body] || '')
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
# this is to approximate `curl -v`s output. but it's all faked, whereas curl gives you
|
86
|
+
# the real text written and read for request and response. whatever, close enough.
|
87
|
+
class FaradayCurlVOutputter < FaradayOutputter
|
88
|
+
|
89
|
+
# defines a method with the given name, applying coloring defined by any additional arguments.
|
90
|
+
# if $options[:color] is set, respects that; otherwise, applies color if the output device is a tty.
|
91
|
+
def self.color(name, *color_args)
|
92
|
+
define_method(name) do |arg|
|
93
|
+
if color?
|
94
|
+
require 'term/ansicolor'
|
95
|
+
color_args.inject(arg) do |result, color_arg|
|
96
|
+
Term::ANSIColor.send(color_arg, result)
|
97
|
+
end
|
98
|
+
else
|
99
|
+
arg
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
color :info, :intense_yellow
|
105
|
+
color :info_body, :yellow
|
106
|
+
color :protocol
|
107
|
+
|
108
|
+
color :request, :intense_cyan
|
109
|
+
color :request_verb, :bold
|
110
|
+
color :request_header
|
111
|
+
color :request_blankline, :intense_cyan, :bold
|
112
|
+
|
113
|
+
color :response, :intense_green
|
114
|
+
color :response_status, :bold, :green
|
115
|
+
color :response_header
|
116
|
+
color :response_blankline, :intense_green, :bold
|
117
|
+
|
118
|
+
def call(request_env)
|
119
|
+
@outdev.puts "#{info('*')} #{info_body("connect to #{request_env[:url].host} on port #{request_env[:url].port}")}"
|
120
|
+
@outdev.puts "#{info('*')} #{info_body("getting our SSL on")}" if request_env[:url].scheme=='https'
|
121
|
+
@outdev.puts "#{request('>')} #{request_verb(request_env[:method].to_s.upcase)} #{request_env[:url].request_uri} #{protocol('HTTP/1.1' || 'or something - TODO')}"
|
122
|
+
request_env[:request_headers].each do |k, v|
|
123
|
+
@outdev.puts "#{request('>')} #{request_header(k)}#{request(':')} #{v}"
|
124
|
+
end
|
125
|
+
@outdev.puts "#{request_blankline('>')} "
|
126
|
+
request_body = color_body_by_content_type(request_env[:body], request_env[:request_headers]['Content-Type'])
|
127
|
+
(request_body || '').split("\n", -1).each do |line|
|
128
|
+
@outdev.puts "#{request('>')} #{line}"
|
129
|
+
end
|
130
|
+
@app.call(request_env).on_complete do |response_env|
|
131
|
+
@outdev.puts "#{response('<')} #{protocol('HTTP/1.1' || 'or something - TODO')} #{response_status(response_env[:status].to_s)}"
|
132
|
+
request_env[:response_headers].each do |k, v|
|
133
|
+
@outdev.puts "#{response('<')} #{response_header(k)}#{response(':')} #{v}"
|
134
|
+
end
|
135
|
+
@outdev.puts "#{response_blankline ('<')} "
|
136
|
+
response_body = color_body_by_content_type(response_env[:body], response_env[:response_headers]['Content-Type'])
|
137
|
+
(response_body || '').split("\n", -1).each do |line|
|
138
|
+
@outdev.puts "#{response('<')} #{line}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# whether to use color
|
144
|
+
def color?
|
145
|
+
$options[:color].nil? ? @outdev.tty? : $options[:color]
|
146
|
+
end
|
147
|
+
|
148
|
+
# a mapping for each registered CodeRay scanner to the Media Types which represent
|
149
|
+
# that language. extremely incomplete!
|
150
|
+
CodeRayForMediaTypes = {
|
151
|
+
:c => [],
|
152
|
+
:cpp => [],
|
153
|
+
:clojure => [],
|
154
|
+
:css => ['text/css', 'application/css-stylesheet'],
|
155
|
+
:delphi => [],
|
156
|
+
:diff => [],
|
157
|
+
:erb => [],
|
158
|
+
:groovy => [],
|
159
|
+
:haml => [],
|
160
|
+
:html => ['text/html'],
|
161
|
+
:java => [],
|
162
|
+
:java_script => ['application/javascript', 'text/javascript', 'application/x-javascript'],
|
163
|
+
:json => ['application/json'],
|
164
|
+
:php => [],
|
165
|
+
:python => ['text/x-python'],
|
166
|
+
:ruby => [],
|
167
|
+
:sql => [],
|
168
|
+
:xml => ['text/xml', 'application/xml', %r(\Aapplication/.*\+xml\z)],
|
169
|
+
:yaml => [],
|
170
|
+
}
|
171
|
+
|
172
|
+
# takes a body and a content type; returns the body, with coloring (ansi colors for terminals)
|
173
|
+
# possibly added, if it's a recognized content type and #color? is true
|
174
|
+
def color_body_by_content_type(body, content_type)
|
175
|
+
if body && color?
|
176
|
+
# kinda hacky way to get the media_type. faraday should supply this ...
|
177
|
+
require 'rack'
|
178
|
+
media_type = ::Rack::Request.new({'CONTENT_TYPE' => content_type}).media_type
|
179
|
+
coderay_scanner = CodeRayForMediaTypes.reject{|k,v| !v.any?{|type| type === media_type} }.keys.first
|
180
|
+
if coderay_scanner
|
181
|
+
require 'coderay'
|
182
|
+
body = CodeRay.scan(body, coderay_scanner).encode(:terminal)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
body
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
# CONFIGURE THE FARADAY CONNECTION
|
190
|
+
faraday_options = {}
|
191
|
+
if $options[:no_ssl_verify]
|
192
|
+
faraday_options[:ssl] = {:verify => false}
|
193
|
+
end
|
194
|
+
connection = Faraday.new(faraday_options) do |builder|
|
195
|
+
if $oauth.any?
|
196
|
+
$oauth[:signature_method] ||= 'HMAC-SHA1'
|
197
|
+
require 'oauthenticator'
|
198
|
+
OAuthenticator::FaradaySigner
|
199
|
+
builder.use OAuthenticator::FaradaySigner, $oauth
|
200
|
+
end
|
201
|
+
builder.use $options[:verbose] ? FaradayCurlVOutputter : FaradayOutputter
|
202
|
+
builder.adapter Faraday.default_adapter
|
203
|
+
end
|
204
|
+
|
205
|
+
httpmethod, url, body = *ARGV
|
206
|
+
|
207
|
+
unless Faraday::Connection::METHODS.map{|m| m.to_s.downcase }.include?(httpmethod.downcase)
|
208
|
+
abort "Unrecognized HTTP method given: #{httpmethod}\n\n" + opt_parser.help
|
209
|
+
end
|
210
|
+
|
211
|
+
headers = $options[:headers]
|
212
|
+
if body && !headers['Content-Type'.downcase]
|
213
|
+
# I'd rather not have a default content-type, but if none is set then the HTTP adapter sets this to
|
214
|
+
# application/x-www-form-urlencoded anyway. application/json is a better default for our purposes.
|
215
|
+
headers['Content-Type'.downcase] = 'application/json'
|
216
|
+
end
|
217
|
+
|
218
|
+
# OH LOOK IT'S FINALLY ACTUALLY CONNECTING TO SOMETHING
|
219
|
+
|
220
|
+
response = connection.run_request(httpmethod.downcase.to_sym, url, body, headers)
|
@@ -0,0 +1,31 @@
|
|
1
|
+
class PublicForwarder < BasicObject
|
2
|
+
def initialize(object)
|
3
|
+
@object=object
|
4
|
+
end
|
5
|
+
|
6
|
+
# forwards public methods to the object. attempting to invoke private or
|
7
|
+
# protected methods raises, as it would if the object was a normal receiver.
|
8
|
+
def method_missing(method, *args, &block)
|
9
|
+
if @object.protected_methods.any?{|pm| pm.to_s == method.to_s }
|
10
|
+
::Kernel.raise ::NoMethodError, "protected method `#{method}' called for #{@object.inspect}"
|
11
|
+
elsif @object.private_methods.any?{|pm| pm.to_s == method.to_s }
|
12
|
+
::Kernel.raise ::NoMethodError, "private method `#{method}' called for #{@object.inspect}"
|
13
|
+
else
|
14
|
+
@object.__send__(method, *args, &block)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class Object
|
20
|
+
# like instance_exec, but only gives access to public methods. no private or protected methods, no
|
21
|
+
# instance variables.
|
22
|
+
def public_instance_exec(*args, &block)
|
23
|
+
PublicForwarder.new(self).instance_exec(*args, &block)
|
24
|
+
end
|
25
|
+
|
26
|
+
# like instance_eval, but only gives access to public methods. no private or protected methods, no
|
27
|
+
# instance variables.
|
28
|
+
def public_instance_eval(&block)
|
29
|
+
PublicForwarder.new(self).instance_eval(&block)
|
30
|
+
end
|
31
|
+
end
|
data/lib/api_hammer/version.rb
CHANGED
@@ -0,0 +1,43 @@
|
|
1
|
+
proc { |p| $:.unshift(p) unless $:.any? { |lp| File.expand_path(lp) == p } }.call(File.expand_path('.', File.dirname(__FILE__)))
|
2
|
+
require 'helper'
|
3
|
+
|
4
|
+
require 'api_hammer/public_instance_exec'
|
5
|
+
|
6
|
+
class Foo
|
7
|
+
def public_method(arg = :public)
|
8
|
+
arg
|
9
|
+
end
|
10
|
+
protected
|
11
|
+
def protected_method(arg = :protected)
|
12
|
+
arg
|
13
|
+
end
|
14
|
+
private
|
15
|
+
def private_method(arg = :private)
|
16
|
+
arg
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#public_instance_exec' do
|
21
|
+
it 'does things' do
|
22
|
+
foo = Foo.new
|
23
|
+
assert_equal(:public_exec, foo.public_instance_exec(:public_exec) { |arg| public_method(arg) })
|
24
|
+
regularex = (foo.protected_method rescue $!)
|
25
|
+
ex = assert_raises(regularex.class) { foo.public_instance_exec(:protected_exec) { |arg| protected_method(arg) } }
|
26
|
+
assert_equal(regularex.message, ex.message)
|
27
|
+
regularex = (foo.private_method rescue $!)
|
28
|
+
ex = assert_raises(regularex.class) { foo.public_instance_exec(:private_exec) { |arg| private_method(arg) } }
|
29
|
+
assert_equal(regularex.message, ex.message)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
describe '#public_instance_eval' do
|
33
|
+
it 'does things' do
|
34
|
+
foo = Foo.new
|
35
|
+
assert_equal(:public, foo.public_instance_eval { public_method })
|
36
|
+
regularex = (foo.protected_method rescue $!)
|
37
|
+
ex = assert_raises(regularex.class) { foo.public_instance_eval { protected_method } }
|
38
|
+
assert_equal(regularex.message, ex.message)
|
39
|
+
regularex = (foo.private_method rescue $!)
|
40
|
+
ex = assert_raises(regularex.class) { foo.public_instance_eval { private_method } }
|
41
|
+
assert_equal(regularex.message, ex.message)
|
42
|
+
end
|
43
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: api_hammer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ethan
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2014-05-
|
11
|
+
date: 2014-05-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: coderay
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
- !ruby/object:Gem::Dependency
|
70
84
|
name: rake
|
71
85
|
requirement: !ruby/object:Gem::Requirement
|
@@ -140,7 +154,8 @@ description: actually a set of small API-related tools. very much unlike a hamme
|
|
140
154
|
at all, which is one large tool.
|
141
155
|
email:
|
142
156
|
- ethan@unth
|
143
|
-
executables:
|
157
|
+
executables:
|
158
|
+
- hc
|
144
159
|
extensions: []
|
145
160
|
extra_rdoc_files: []
|
146
161
|
files:
|
@@ -150,9 +165,11 @@ files:
|
|
150
165
|
- LICENSE.txt
|
151
166
|
- README.md
|
152
167
|
- Rakefile.rb
|
168
|
+
- bin/hc
|
153
169
|
- lib/api_hammer.rb
|
154
170
|
- lib/api_hammer/check_required_params.rb
|
155
171
|
- lib/api_hammer/halt.rb
|
172
|
+
- lib/api_hammer/public_instance_exec.rb
|
156
173
|
- lib/api_hammer/rails.rb
|
157
174
|
- lib/api_hammer/request_logger.rb
|
158
175
|
- lib/api_hammer/show_text_exceptions.rb
|
@@ -165,6 +182,7 @@ files:
|
|
165
182
|
- test/check_required_params_test.rb
|
166
183
|
- test/halt_test.rb
|
167
184
|
- test/helper.rb
|
185
|
+
- test/public_instance_exec_test.rb
|
168
186
|
- test/request_logger_test.rb
|
169
187
|
- test/show_text_exceptions_test.rb
|
170
188
|
- test/trailing_newline_test.rb
|
@@ -197,6 +215,7 @@ test_files:
|
|
197
215
|
- test/check_required_params_test.rb
|
198
216
|
- test/halt_test.rb
|
199
217
|
- test/helper.rb
|
218
|
+
- test/public_instance_exec_test.rb
|
200
219
|
- test/request_logger_test.rb
|
201
220
|
- test/show_text_exceptions_test.rb
|
202
221
|
- test/trailing_newline_test.rb
|