yobi-http 0.12.3 → 0.16.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/Examples.md +7 -0
- data/README.md +4 -0
- data/exe/yobi +27 -5
- data/lib/yobi/cli/arguments.rb +58 -5
- data/lib/yobi/http.rb +21 -8
- data/lib/yobi/version.rb +1 -1
- data/spec/yobi/cli/arguments_spec.rb +47 -0
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3558906f242a7670158833610ad7b062bf25e308e4985804241bdba30a9d6968
|
|
4
|
+
data.tar.gz: d96e600ccdf03ebcddb0c9ce14a3a2c141fb1a1ba2ffe04ee7cdb0a1a5383c7d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 68ec04f8b731ebc0e9f508ed332d6414292963d1dbbd97aaba96a360e930ce3d1421812d11c1031305126633db9ad012fb2f4efa6e170aba4e09e3eeef37cf3e
|
|
7
|
+
data.tar.gz: 61c69a1bfdc761df2c791396fb84a3ce5183bf24b71ffb4073fbebdbd1e86b3e5e6986fbeeb977a7c2225624d72d083270e585e0afe186030066b3dab05f0e6c
|
data/Examples.md
CHANGED
|
@@ -23,6 +23,13 @@ yobi --debug POST https://httpbin.org/post name=Yobi type="HTTP client"
|
|
|
23
23
|
yobi --debug POST https://httpbin.org/post name=Yobi type="HTTP client" --form
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
+
## HTTP request with complex JSON body
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
yobi --debug POST https://httpbin.org/post name=Yobi meta:='{"category": "HTTP client", "type": "rubygem", "year": 2026 }' released:=true tags:'["ruby", "http", "gem"]'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
|
|
26
33
|
## HTTP request with query parameters
|
|
27
34
|
|
|
28
35
|
```bash
|
data/README.md
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
[](https://badge.fury.io/rb/yobi-http)
|
|
2
|
+
[](https://opensource.org/licenses/MIT)
|
|
3
|
+
[](https://qlty.sh/gh/dvinciguerra/projects/yobi-http)
|
|
4
|
+
|
|
1
5
|
# Yobi(呼び) Http Client
|
|
2
6
|
|
|
3
7
|
Yobi is a Ruby gem that provides a simple and efficient way to make HTTP requests. It is designed to be easy to use
|
data/exe/yobi
CHANGED
|
@@ -16,7 +16,7 @@ require "yobi"
|
|
|
16
16
|
# parsing arguments
|
|
17
17
|
@options = {
|
|
18
18
|
print: "HB", auth: nil, auth_type: "basic", verbose: false, raw: false, offline: false, follow: false, debug: false,
|
|
19
|
-
timeout: nil, download: false
|
|
19
|
+
timeout: nil, download: false, output: nil, content_type: :json
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
parser = OptionParser.new do |opts|
|
|
@@ -28,7 +28,7 @@ parser = OptionParser.new do |opts|
|
|
|
28
28
|
|
|
29
29
|
Examples:
|
|
30
30
|
yobi https://api.example.com/users
|
|
31
|
-
yobi POST https://api.example.com/
|
|
31
|
+
yobi POST https://api.example.com/products name="New Product" price:=19.99 enabled:=true tags:=@tags.json
|
|
32
32
|
yobi GET :8080/api/items Authorization:"Bearer $TOKEN"
|
|
33
33
|
yobi -A basic -a user:pass https://api.example.com/secure-data
|
|
34
34
|
yobi --raw --print B POST https://httpbin.org/anything | jq '.headers["User-Agent"]'
|
|
@@ -53,6 +53,27 @@ parser = OptionParser.new do |opts|
|
|
|
53
53
|
@options[:raw] = true
|
|
54
54
|
end
|
|
55
55
|
|
|
56
|
+
opts.on("--form", "Send data as application/x-www-form-urlencoded instead of JSON") do
|
|
57
|
+
@options[:content_type] = :form
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
opts.on("--multipart", "Send data as multipart/form-data instead of JSON") do
|
|
61
|
+
@options[:content_type] = :multipart
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
opts.on("--xml", "Send data as application/xml instead of JSON") do
|
|
65
|
+
@options[:content_type] = :xml
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
opts.on("--binary", "Send data as application/octet-stream instead of JSON") do
|
|
69
|
+
@options[:content_type] = :binary
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
opts.on("--json", "Send data as application/json (default)") do
|
|
73
|
+
# This option is implicit since JSON is the default, but we can set it explicitly if needed
|
|
74
|
+
@options[:content_type] = :json
|
|
75
|
+
end
|
|
76
|
+
|
|
56
77
|
opts.on("-h", "--help", "Print this help message") do
|
|
57
78
|
puts opts
|
|
58
79
|
exit
|
|
@@ -101,13 +122,14 @@ method = Yobi.args.http_method?(ARGV[0]) ? ARGV.shift : "GET"
|
|
|
101
122
|
# resolve the URL
|
|
102
123
|
url = Yobi.args.url(ARGV.shift)
|
|
103
124
|
|
|
104
|
-
data = Yobi.args.parse_data(ARGV)
|
|
105
|
-
|
|
125
|
+
data = Yobi.args.parse_data(ARGV, @options)
|
|
126
|
+
query = Yobi.args.parse_query(ARGV, @options)
|
|
127
|
+
headers = Yobi.args.parse_headers(ARGV, @options)
|
|
106
128
|
|
|
107
129
|
# prepare authentication header if auth is provided
|
|
108
130
|
Yobi.args.auth_header(headers, @options) if @options[:auth]
|
|
109
131
|
|
|
110
|
-
Yobi::Http.request(method, url, data: data, headers: headers, options: @options) do |http, request|
|
|
132
|
+
Yobi::Http.request(method, url, data: data, headers: headers, query: query, options: @options) do |http, request|
|
|
111
133
|
options = @options
|
|
112
134
|
|
|
113
135
|
# follow redirects, offline, download or standard request mode
|
data/lib/yobi/cli/arguments.rb
CHANGED
|
@@ -22,13 +22,47 @@ module Yobi
|
|
|
22
22
|
end
|
|
23
23
|
end
|
|
24
24
|
|
|
25
|
-
def
|
|
26
|
-
args.select { |arg| arg.
|
|
25
|
+
def parse_query(args, _options)
|
|
26
|
+
args.select { |arg| arg.match?(/::/) }.map.to_h { |arg| arg.split("::", 2).map(&:strip) }
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
30
|
+
def parse_data(args, _options)
|
|
31
|
+
args.select { |arg| arg.match?("^.*(={1}|:=|=@|:=@).*$") }.map do |arg|
|
|
32
|
+
case arg
|
|
33
|
+
when /:=@/
|
|
34
|
+
arg.split(":=@", 2).map do |part|
|
|
35
|
+
part = String(part)&.strip
|
|
36
|
+
File.exist?(part) ? JSON.parse(File.read(part)) : part
|
|
37
|
+
end
|
|
38
|
+
when /:=/
|
|
39
|
+
arg.split(/:=/, 2).map.with_index do |part, index|
|
|
40
|
+
part = String(part)&.strip
|
|
41
|
+
index.zero? ? part : JSON.parse(part)
|
|
42
|
+
rescue JSON::ParserError => e
|
|
43
|
+
warn "Error #{e}: #{part}"
|
|
44
|
+
exit 1
|
|
45
|
+
end
|
|
46
|
+
when /=@/
|
|
47
|
+
arg.split(/=@/, 2).map do |part|
|
|
48
|
+
part = String(part)&.strip
|
|
49
|
+
File.exist?(part) ? File.read(part)&.strip : part
|
|
50
|
+
end
|
|
51
|
+
else
|
|
52
|
+
arg.split("=", 2).map(&:strip)
|
|
53
|
+
end
|
|
54
|
+
end.compact.to_h
|
|
55
|
+
end
|
|
56
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
57
|
+
|
|
58
|
+
def parse_headers(args, options)
|
|
59
|
+
content_type = content_type_for options[:content_type]
|
|
60
|
+
|
|
61
|
+
{ "Content-Type" => content_type, "User-Agent" => "#{Yobi.name}/#{Yobi::VERSION}" }.merge(
|
|
62
|
+
args
|
|
63
|
+
.select { |arg| arg.match?(/:{1}/) && !arg.match?(/:=/) && !arg.match?(/::/) }
|
|
64
|
+
.map.to_h { |arg| arg.split(/:{1}/, 2).map(&:strip) }
|
|
65
|
+
)
|
|
32
66
|
end
|
|
33
67
|
|
|
34
68
|
def auth_header(headers, options)
|
|
@@ -46,6 +80,25 @@ module Yobi
|
|
|
46
80
|
exit 1
|
|
47
81
|
end
|
|
48
82
|
end
|
|
83
|
+
|
|
84
|
+
private
|
|
85
|
+
|
|
86
|
+
def content_type_for(value)
|
|
87
|
+
case value
|
|
88
|
+
when :form
|
|
89
|
+
"application/x-www-form-urlencoded"
|
|
90
|
+
when :multipart
|
|
91
|
+
"multipart/form-data"
|
|
92
|
+
when :xml
|
|
93
|
+
"application/xml"
|
|
94
|
+
when :binary
|
|
95
|
+
"application/octet-stream"
|
|
96
|
+
when :json, nil
|
|
97
|
+
"application/json"
|
|
98
|
+
else
|
|
99
|
+
"application/json"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
49
102
|
end
|
|
50
103
|
end
|
|
51
104
|
end
|
data/lib/yobi/http.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "json"
|
|
3
4
|
require "net/http"
|
|
4
5
|
|
|
5
6
|
module Yobi
|
|
@@ -7,10 +8,22 @@ module Yobi
|
|
|
7
8
|
module Http
|
|
8
9
|
METHODS = %w[GET POST PUT DELETE PATCH HEAD OPTIONS].freeze
|
|
9
10
|
|
|
11
|
+
CONTENT_TYPES = {
|
|
12
|
+
text: "text/plain",
|
|
13
|
+
html: "text/html",
|
|
14
|
+
form: "application/x-www-form-urlencoded",
|
|
15
|
+
multipart: "multipart/form-data",
|
|
16
|
+
xml: "application/xml",
|
|
17
|
+
binary: "application/octet-stream",
|
|
18
|
+
json: "application/json"
|
|
19
|
+
}.freeze
|
|
20
|
+
|
|
10
21
|
class << self
|
|
11
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
12
|
-
def request(method, url, data: {}, headers: {}, options: {})
|
|
22
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
|
|
23
|
+
def request(method, url, data: {}, query: {}, headers: {}, options: {})
|
|
13
24
|
@uri = URI(url)
|
|
25
|
+
@uri.query = URI.encode_www_form(**query) unless query.empty?
|
|
26
|
+
|
|
14
27
|
@options = options
|
|
15
28
|
@method = method.capitalize
|
|
16
29
|
|
|
@@ -33,7 +46,7 @@ module Yobi
|
|
|
33
46
|
exit 1
|
|
34
47
|
end
|
|
35
48
|
end
|
|
36
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
49
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists
|
|
37
50
|
|
|
38
51
|
# rubocop:disable Metrics/ParameterLists
|
|
39
52
|
def follow_redirects(response, url, method, data, headers, options)
|
|
@@ -50,7 +63,7 @@ module Yobi
|
|
|
50
63
|
end
|
|
51
64
|
# rubocop:enable Metrics/ParameterLists
|
|
52
65
|
|
|
53
|
-
# rubocop:disable Metrics/
|
|
66
|
+
# rubocop:disable Metrics/AbcSize
|
|
54
67
|
def offline_mode(_request, options)
|
|
55
68
|
Net::HTTP.class_eval do
|
|
56
69
|
def connect; end
|
|
@@ -59,7 +72,7 @@ module Yobi
|
|
|
59
72
|
options[:verbose] = true
|
|
60
73
|
|
|
61
74
|
Net::HTTPResponse.new("1.1", "200", "OK").tap do |response|
|
|
62
|
-
response["Content-Type"] =
|
|
75
|
+
response["Content-Type"] = CONTENT_TYPES.fetch(options[:content_type], CONTENT_TYPES[:json])
|
|
63
76
|
response["Access-Control-Allow-Credentials"] = true
|
|
64
77
|
response["Access-Control-Allow-Origin"] = "*"
|
|
65
78
|
response["Connection"] = "close"
|
|
@@ -71,9 +84,9 @@ module Yobi
|
|
|
71
84
|
response.instance_variable_set(:@read, true)
|
|
72
85
|
end
|
|
73
86
|
end
|
|
74
|
-
# rubocop:enable Metrics/
|
|
87
|
+
# rubocop:enable Metrics/AbcSize
|
|
75
88
|
|
|
76
|
-
# rubocop:disable Metrics/
|
|
89
|
+
# rubocop:disable Metrics/AbcSize
|
|
77
90
|
def download(request, http, options)
|
|
78
91
|
http.request(request) do |response|
|
|
79
92
|
url = request.uri.to_s
|
|
@@ -93,7 +106,7 @@ module Yobi
|
|
|
93
106
|
|
|
94
107
|
exit 0
|
|
95
108
|
end
|
|
96
|
-
# rubocop:enable Metrics/
|
|
109
|
+
# rubocop:enable Metrics/AbcSize
|
|
97
110
|
end
|
|
98
111
|
end
|
|
99
112
|
end
|
data/lib/yobi/version.rb
CHANGED
|
@@ -59,6 +59,53 @@ RSpec.describe Yobi::CLI::Arguments do
|
|
|
59
59
|
|
|
60
60
|
expect(described_class.parse_data(args)).to eq({ "name" => "John", "age" => "30" })
|
|
61
61
|
end
|
|
62
|
+
|
|
63
|
+
context "when given an json string" do
|
|
64
|
+
it "parses the json string into a hash" do
|
|
65
|
+
args = ['data:={ "name": "John", "age": 30, "admin": false, "friends": ["Jane", "Doe"] }']
|
|
66
|
+
|
|
67
|
+
expect(described_class.parse_data(args))
|
|
68
|
+
.to eq({ "data" => { "name" => "John", "age" => 30, "admin" => false, "friends" => %w[Jane Doe] } })
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
it "raises an error if the json string is invalid" do
|
|
72
|
+
args = ['data:={"name": "John", "age": 30, "city": "New York"'] # Missing closing brace
|
|
73
|
+
|
|
74
|
+
expect { described_class.parse_data(args) }.to raise_error(SystemExit)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
context "when given a json file path" do
|
|
79
|
+
let(:json_content) do
|
|
80
|
+
{
|
|
81
|
+
"title" => "Sample JSON",
|
|
82
|
+
"description" => "This is a sample JSON file.",
|
|
83
|
+
"version" => 1.0,
|
|
84
|
+
"tags" => %w[sample json example],
|
|
85
|
+
"metadata" => {
|
|
86
|
+
"author" => "John Doe",
|
|
87
|
+
"created" => "2024-06-01T12:00:00Z",
|
|
88
|
+
"updated" => "2024-06-10T15:30:00Z"
|
|
89
|
+
},
|
|
90
|
+
"published" => true,
|
|
91
|
+
"more_info" => nil
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
before do
|
|
96
|
+
File.write("sample.json", json_content.to_json)
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
after do
|
|
100
|
+
File.delete("sample.json") if File.exist?("sample.json")
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "parses the json file content into a hash" do
|
|
104
|
+
args = ["data:=@sample.json"]
|
|
105
|
+
|
|
106
|
+
expect(described_class.parse_data(args)).to eq({ "data" => json_content })
|
|
107
|
+
end
|
|
108
|
+
end
|
|
62
109
|
end
|
|
63
110
|
|
|
64
111
|
describe ".parse_headers" do
|
metadata
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yobi-http
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.16.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Vinciguerra
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-
|
|
10
|
+
date: 2026-03-05 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: base64
|