yobi-http 0.10.0 → 0.12.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 +72 -0
- data/exe/yobi +23 -71
- data/lib/yobi/http.rb +70 -2
- data/lib/yobi/renders/colored.rb +31 -0
- data/lib/yobi/renders/raw.rb +96 -0
- data/lib/yobi/renders.rb +5 -0
- data/lib/yobi/ui/progress.rb +80 -0
- data/lib/yobi/ui.rb +5 -0
- data/lib/yobi/version.rb +1 -1
- data/lib/yobi.rb +2 -0
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 713eaa43f4787691ddcf301d17652886a9f1141cd92600924a0b4737b87f0637
|
|
4
|
+
data.tar.gz: ec5199666420337034a8f2d17a011dbdb0c5acde3851f2b533ef333c049b9c3a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8cb41090b4a1b2c2b89795d86d22ec593395429f50a89f9655857ab7ab2cd46b9e03bec8b6843bdc492c625614c6f9a8976566cebe0b77deb06bb6a584fe062b
|
|
7
|
+
data.tar.gz: d1a04f1b46bda0d9786d151477d76c25bd981b60c9c6a2a0e80a39a46b05a64d45da695578d39774fecabdbb6fd3726fb87a7c45fd5ccf231e55becd73b9a617
|
data/Examples.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
|
|
2
|
+
# Yobi HTTP - Usage examples
|
|
3
|
+
|
|
4
|
+
Some examples of using the
|
|
5
|
+
|
|
6
|
+
Some examples of using the Yobi HTTP CLI.
|
|
7
|
+
|
|
8
|
+
## Simple HTTP Get request
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
yobi --debug https://httpbin.org/get
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## HTTP POST request with JSON body
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
yobi --debug POST https://httpbin.org/post name=Yobi type="HTTP client"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## WIP: HTTP POST request with form data
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
yobi --debug POST https://httpbin.org/post name=Yobi type="HTTP client" --form
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## HTTP request with query parameters
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
yobi --debug https://httpbin.org/get name=Yobi type="HTTP client"
|
|
30
|
+
```
|
|
31
|
+
## HTTP request with basic authentication
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
yobi --debug https://httpbin.org/basic-auth/user/passwd --auth user:passwd --auth-type basic
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## HTTP request with timeout
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
yobi --debug --timeout 5 https://httpbin.org/delay/10
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## HTTP request with custom headers
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
yobi --debug https://httpbin.org/headers X-Custom-Header:Yobi User-Agent:"Yobi HTTP Client"
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Download a file
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
yobi --debug --download https://ash-speed.hetzner.com/100MB.bin -o sample.bin
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## HTTP request with follow redirects
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
yobi --debug --follow https://httpbin.org/redirect/3
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## HTTP request with output to a file
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
yobi -p B https://httpbin.org/get -o response.json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## HTTP request with verbose output
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
yobi --debug --verbose https://httpbin.org/get
|
|
71
|
+
```
|
|
72
|
+
|
data/exe/yobi
CHANGED
|
@@ -15,7 +15,8 @@ require "yobi"
|
|
|
15
15
|
|
|
16
16
|
# parsing arguments
|
|
17
17
|
@options = {
|
|
18
|
-
print: "HB", auth: nil, auth_type: "basic", verbose: false, raw: false, offline: false, follow: false, debug: false
|
|
18
|
+
print: "HB", auth: nil, auth_type: "basic", verbose: false, raw: false, offline: false, follow: false, debug: false,
|
|
19
|
+
timeout: nil, download: false
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
parser = OptionParser.new do |opts|
|
|
@@ -36,8 +37,8 @@ parser = OptionParser.new do |opts|
|
|
|
36
37
|
)
|
|
37
38
|
|
|
38
39
|
opts.on("-p", "--print FORMAT", "Specify the output format (e.g., 'H' for headers, 'B' for body)") do |format|
|
|
39
|
-
@options[:print] = format
|
|
40
|
-
warn "[Argument Error] Unsupported format: #{format}" unless format.match?(/\A[
|
|
40
|
+
@options[:print] = format || "HB" # default to printing both headers and body
|
|
41
|
+
warn "[Argument Error] Unsupported format: #{format}" unless format.match?(/\A[HBhb]+\z/)
|
|
41
42
|
end
|
|
42
43
|
|
|
43
44
|
opts.on("-a", "--auth USER:PASS", "Specify basic authentication credentials") do |auth|
|
|
@@ -69,9 +70,16 @@ parser = OptionParser.new do |opts|
|
|
|
69
70
|
@options[:output] = file
|
|
70
71
|
end
|
|
71
72
|
|
|
73
|
+
opts.on("--timeout SECONDS", "Set a custom timeout for the request (integer or float)") do |timeout|
|
|
74
|
+
@options[:timeout] = timeout.to_f
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
opts.on("--download", "Download response body to a file specified by --output") do
|
|
78
|
+
@options[:download] = true
|
|
79
|
+
end
|
|
80
|
+
|
|
72
81
|
opts.on("-v", "--verbose", "Print detailed request and response information") do
|
|
73
82
|
@options[:verbose] = true
|
|
74
|
-
|
|
75
83
|
end
|
|
76
84
|
|
|
77
85
|
opts.on("--debug", "Print debug information") do
|
|
@@ -99,81 +107,25 @@ headers = Yobi.args.parse_headers(ARGV)
|
|
|
99
107
|
# prepare authentication header if auth is provided
|
|
100
108
|
Yobi.args.auth_header(headers, @options) if @options[:auth]
|
|
101
109
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
# Extend Net::HTTPResponse to add util behaviors
|
|
105
|
-
def offline_mode(request, options)
|
|
106
|
-
Net::HTTP.class_eval do
|
|
107
|
-
def connect; end
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
options[:verbose] = true
|
|
111
|
-
|
|
112
|
-
response = Net::HTTPResponse.new("1.1", "200", "OK")
|
|
113
|
-
response["Content-Type"] = "application/json"
|
|
114
|
-
response["Access-Control-Allow-Credentials"] = true
|
|
115
|
-
response["Access-Control-Allow-Origin"] = "*"
|
|
116
|
-
response["Connection"] = "close"
|
|
117
|
-
response["Date"] = Time.now.httpdate
|
|
118
|
-
response["Server"] = "yobi-offline/#{Yobi::VERSION}"
|
|
119
|
-
response["X-Powered-By"] = "Yobi/#{Yobi::VERSION}"
|
|
120
|
-
response.body = body = JSON.pretty_generate({ message: "Offline mode enabled" })
|
|
121
|
-
|
|
122
|
-
view = Yobi.view(:output)
|
|
123
|
-
puts TTY::Markdown.parse(view.result(binding), color: :always)
|
|
124
|
-
|
|
125
|
-
exit 0
|
|
126
|
-
end
|
|
127
|
-
|
|
128
|
-
def raw_mode(_request, response, options)
|
|
129
|
-
if options[:print].include?("H")
|
|
130
|
-
puts "HTTP/#{response.http_version} #{response.code} #{response.message}"
|
|
131
|
-
response.each_header { |key, value| puts "#{key}: #{value}" }
|
|
132
|
-
puts
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
body = response.parsed_body
|
|
136
|
-
puts body if options[:print].include?("B") && body
|
|
137
|
-
|
|
138
|
-
exit 0
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def follow_redirects(response, url, method, data, headers, options)
|
|
142
|
-
return response unless response.is_a?(Net::HTTPRedirection)
|
|
143
|
-
|
|
144
|
-
location = response["location"]
|
|
145
|
-
warn "Redirected to #{location}" if options[:debug]
|
|
146
|
-
new_url = URI.join(url, location).to_s
|
|
147
|
-
|
|
148
|
-
Yobi.request(method, new_url, data: data, headers: headers, options: options) do |new_http, new_request|
|
|
149
|
-
response = new_http.request(new_request)
|
|
150
|
-
return follow_redirects(response, new_url, method, data, headers, options)
|
|
151
|
-
end
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
Yobi.request(method, url, data: data, headers: headers, options: @options) do |http, request|
|
|
110
|
+
Yobi::Http.request(method, url, data: data, headers: headers, options: @options) do |http, request|
|
|
155
111
|
options = @options
|
|
156
112
|
|
|
157
|
-
|
|
158
|
-
|
|
113
|
+
# follow redirects, offline, download or standard request mode
|
|
159
114
|
response =
|
|
160
115
|
if options[:follow]
|
|
161
|
-
follow_redirects(http.request(request), url, method, data, headers, options)
|
|
116
|
+
Yobi::Http.follow_redirects(http.request(request), url, method, data, headers, options)
|
|
117
|
+
elsif options[:offline]
|
|
118
|
+
Yobi::Http.offline_mode(request, options)
|
|
119
|
+
elsif options[:download]
|
|
120
|
+
Yobi::Http.download(request, http, options)
|
|
162
121
|
else
|
|
163
122
|
http.request(request)
|
|
164
123
|
end
|
|
165
124
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
view = Yobi.view(:output)
|
|
171
|
-
output_result = view.result(binding)
|
|
172
|
-
|
|
173
|
-
if options[:output]
|
|
174
|
-
file = File.expand_path(options[:output], Dir.pwd)
|
|
175
|
-
File.write(file, output_result, mode: "w")
|
|
125
|
+
# render output
|
|
126
|
+
if options[:raw]
|
|
127
|
+
Yobi::Renders::Raw.render(request, response, options)
|
|
176
128
|
else
|
|
177
|
-
|
|
129
|
+
Yobi::Renders::Colored.render(request, response, options)
|
|
178
130
|
end
|
|
179
131
|
end
|
data/lib/yobi/http.rb
CHANGED
|
@@ -8,7 +8,7 @@ module Yobi
|
|
|
8
8
|
METHODS = %w[GET POST PUT DELETE PATCH HEAD OPTIONS].freeze
|
|
9
9
|
|
|
10
10
|
class << self
|
|
11
|
-
# rubocop:disable Metrics/AbcSize
|
|
11
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
12
12
|
def request(method, url, data: {}, headers: {}, options: {})
|
|
13
13
|
@uri = URI(url)
|
|
14
14
|
@options = options
|
|
@@ -18,14 +18,82 @@ module Yobi
|
|
|
18
18
|
request_class = Net::HTTP.const_get(@method)
|
|
19
19
|
request = request_class.new(@uri)
|
|
20
20
|
|
|
21
|
+
if @options[:timeout]
|
|
22
|
+
http.open_timeout = @options[:timeout]
|
|
23
|
+
http.read_timeout = @options[:timeout]
|
|
24
|
+
end
|
|
25
|
+
|
|
21
26
|
headers.each { |key, value| request[key] = value }
|
|
22
27
|
|
|
23
28
|
request.body = data.to_json unless data.empty?
|
|
24
29
|
|
|
25
30
|
yield(http, request) if block_given?
|
|
31
|
+
rescue Net::OpenTimeout, Net::ReadTimeout => e
|
|
32
|
+
warn "Request timed out: #{e.message}"
|
|
33
|
+
exit 1
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
|
37
|
+
|
|
38
|
+
# rubocop:disable Metrics/ParameterLists
|
|
39
|
+
def follow_redirects(response, url, method, data, headers, options)
|
|
40
|
+
return response unless response.is_a?(Net::HTTPRedirection)
|
|
41
|
+
|
|
42
|
+
location = response["location"]
|
|
43
|
+
warn "Redirected to #{location}" if options[:debug]
|
|
44
|
+
new_url = URI.join(url, location).to_s
|
|
45
|
+
|
|
46
|
+
request(method, new_url, data: data, headers: headers, options: options) do |new_http, new_request|
|
|
47
|
+
response = new_http.request(new_request)
|
|
48
|
+
return follow_redirects(response, new_url, method, data, headers, options)
|
|
26
49
|
end
|
|
27
50
|
end
|
|
28
|
-
# rubocop:enable Metrics/
|
|
51
|
+
# rubocop:enable Metrics/ParameterLists
|
|
52
|
+
|
|
53
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
54
|
+
def offline_mode(_request, options)
|
|
55
|
+
Net::HTTP.class_eval do
|
|
56
|
+
def connect; end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
options[:verbose] = true
|
|
60
|
+
|
|
61
|
+
Net::HTTPResponse.new("1.1", "200", "OK").tap do |response|
|
|
62
|
+
response["Content-Type"] = "application/json"
|
|
63
|
+
response["Access-Control-Allow-Credentials"] = true
|
|
64
|
+
response["Access-Control-Allow-Origin"] = "*"
|
|
65
|
+
response["Connection"] = "close"
|
|
66
|
+
response["Date"] = Time.now.httpdate
|
|
67
|
+
response["Server"] = "yobi-offline/#{Yobi::VERSION}"
|
|
68
|
+
response["X-Powered-By"] = "Yobi/#{Yobi::VERSION}"
|
|
69
|
+
|
|
70
|
+
response.body = JSON.pretty_generate({ message: "Offline mode enabled" })
|
|
71
|
+
response.instance_variable_set(:@read, true)
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
75
|
+
|
|
76
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
77
|
+
def download(request, http, options)
|
|
78
|
+
http.request(request) do |response|
|
|
79
|
+
url = request.uri.to_s
|
|
80
|
+
total_bytes = response["Content-Length"]&.to_i
|
|
81
|
+
progress = Yobi::UI::Progress.new(total_bytes)
|
|
82
|
+
|
|
83
|
+
filename = options[:output] || File.basename(URI.parse(url).path)
|
|
84
|
+
File.open(filename, "wb") do |file|
|
|
85
|
+
response.read_body do |chunk|
|
|
86
|
+
file.write(chunk)
|
|
87
|
+
progress.increment(chunk.size)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
puts "\nDownload finished: #{filename}"
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
exit 0
|
|
95
|
+
end
|
|
96
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
29
97
|
end
|
|
30
98
|
end
|
|
31
99
|
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yobi
|
|
4
|
+
module Renders
|
|
5
|
+
# Render a colored output of the request and response
|
|
6
|
+
module Colored
|
|
7
|
+
class << self
|
|
8
|
+
def compile(request, response, options = {})
|
|
9
|
+
body =
|
|
10
|
+
JSON.pretty_generate(response.parsed_body) rescue response.body # rubocop:disable Style/RescueModifier
|
|
11
|
+
|
|
12
|
+
view = Yobi.view(:output)
|
|
13
|
+
TTY::Markdown.parse(view.result(binding), color: :always)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def render(request, response, options = {})
|
|
17
|
+
view = compile(request, response, options)
|
|
18
|
+
|
|
19
|
+
if options[:output]
|
|
20
|
+
file_path = ::File.expand_path(options[:output], Dir.pwd)
|
|
21
|
+
::File.write(file_path, view, mode: "wb")
|
|
22
|
+
else
|
|
23
|
+
puts view
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
exit 0
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yobi
|
|
4
|
+
module Renders
|
|
5
|
+
# Render a raw output of the request and response
|
|
6
|
+
module Raw
|
|
7
|
+
class << self
|
|
8
|
+
def compile(request, response, options = {})
|
|
9
|
+
arguments request: request, response: response, options: options
|
|
10
|
+
|
|
11
|
+
buffer.tap do
|
|
12
|
+
compile_request
|
|
13
|
+
compile_response
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def render(request, response, options = {})
|
|
18
|
+
view = compile(request, response, options)
|
|
19
|
+
|
|
20
|
+
if options[:output]
|
|
21
|
+
file_path = ::File.expand_path(options[:output], Dir.pwd)
|
|
22
|
+
::File.write(file_path, view, mode: "wb")
|
|
23
|
+
else
|
|
24
|
+
puts view
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
exit 0
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :request, :response, :options
|
|
33
|
+
|
|
34
|
+
def buffer
|
|
35
|
+
@buffer ||= String.new
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def arguments(**args)
|
|
39
|
+
@request = args[:request]
|
|
40
|
+
@response = args[:response]
|
|
41
|
+
@options = args[:options] || {}
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def show_header?(request_or_response)
|
|
45
|
+
options[:print].include?(request_or_response.is_a?(Net::HTTPResponse) ? "H" : "h")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def show_body?(request_or_response)
|
|
49
|
+
options[:print].include?(request_or_response.is_a?(Net::HTTPResponse) ? "B" : "b")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def compile_request
|
|
53
|
+
compile_request_header
|
|
54
|
+
compile_request_body
|
|
55
|
+
|
|
56
|
+
buffer << "\n" * 2 if show_header?(request) || show_body?(request)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def compile_request_header
|
|
60
|
+
return unless show_header? request
|
|
61
|
+
|
|
62
|
+
buffer << "#{request.method} #{request.path} HTTP/1.1 \n"
|
|
63
|
+
request.each_capitalized.sort.each do |key, value|
|
|
64
|
+
buffer << "#{key}: #{value}\n"
|
|
65
|
+
end
|
|
66
|
+
buffer << "\n"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def compile_request_body
|
|
70
|
+
return unless show_body?(request) && request.body && !request.body.empty?
|
|
71
|
+
|
|
72
|
+
buffer << "#{JSON.pretty_generate(request.parsed_body) rescue request.body}\n" # rubocop:disable Style/RescueModifier
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def compile_response
|
|
76
|
+
compile_response_header
|
|
77
|
+
compile_response_body
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def compile_response_header
|
|
81
|
+
return unless show_header? response
|
|
82
|
+
|
|
83
|
+
buffer << "HTTP/#{response.http_version} #{response.code} #{response.message}\n"
|
|
84
|
+
response.each_header { |key, value| buffer << "#{key}: #{value}\n" }
|
|
85
|
+
buffer << "\n"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def compile_response_body
|
|
89
|
+
return unless response.body && !response.body.empty? && options[:print].include?("B")
|
|
90
|
+
|
|
91
|
+
buffer << "#{JSON.pretty_generate(response.parsed_body)}\n"
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
data/lib/yobi/renders.rb
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Yobi
|
|
4
|
+
module UI
|
|
5
|
+
# Simple terminal progress bar
|
|
6
|
+
class Progress
|
|
7
|
+
BAR_WIDTH = 40
|
|
8
|
+
|
|
9
|
+
STYLE = {
|
|
10
|
+
arrow: { complete: "▸", incomplete: "▹", unknown: "◂▸" },
|
|
11
|
+
asterisk: { complete: "✱", incomplete: "✳", unknown: "✳✱✳" },
|
|
12
|
+
blade: { complete: "▰", incomplete: "▱", unknown: "▱▰▱" },
|
|
13
|
+
block: { complete: "█", incomplete: "░", unknown: "░█░" },
|
|
14
|
+
box: { complete: "■", incomplete: "□", unknown: "□■□" },
|
|
15
|
+
bracket: { complete: "❭", incomplete: " ", unknown: "❬=❭" },
|
|
16
|
+
burger: { complete: "≡", incomplete: " ", unknown: "<≡>" },
|
|
17
|
+
button: { complete: "⦿", incomplete: "⦾", unknown: "⦾⦿⦾" },
|
|
18
|
+
chevron: { complete: "›", incomplete: " ", unknown: "‹=›" },
|
|
19
|
+
circle: { complete: "●", incomplete: "○", unknown: "○●○" },
|
|
20
|
+
classic: { complete: "=", incomplete: " ", unknown: "<=>" },
|
|
21
|
+
crate: { complete: "▣", incomplete: "⬚", unknown: "⬚▣⬚" },
|
|
22
|
+
diamond: { complete: "♦", incomplete: "♢", unknown: "♢♦♢" },
|
|
23
|
+
dot: { complete: "・", incomplete: " ", unknown: "・・・" },
|
|
24
|
+
heart: { complete: "♥", incomplete: "♡", unknown: "♡♥♡" },
|
|
25
|
+
star: { complete: "★", incomplete: "☆", unknown: "☆★☆" }
|
|
26
|
+
}.freeze
|
|
27
|
+
|
|
28
|
+
def initialize(total_bytes = nil, options = { style: :block })
|
|
29
|
+
@total = total_bytes
|
|
30
|
+
@downloaded = 0
|
|
31
|
+
@last_draw = Time.now
|
|
32
|
+
@options = options
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def increment(bytes)
|
|
36
|
+
@downloaded += bytes
|
|
37
|
+
current_time = Time.now
|
|
38
|
+
|
|
39
|
+
return unless current_time - @last_draw >= 0.1 || finished?
|
|
40
|
+
|
|
41
|
+
draw
|
|
42
|
+
@last_draw = current_time
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def finished?
|
|
46
|
+
@total && @downloaded >= @total
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def draw
|
|
50
|
+
if @total
|
|
51
|
+
percent = @downloaded.to_f / @total
|
|
52
|
+
filled = (percent * BAR_WIDTH).round
|
|
53
|
+
bar = theme[:complete] * filled + theme[:incomplete] * (BAR_WIDTH - filled)
|
|
54
|
+
print "\r[#{bar}] #{(percent * 100).round(1)}% "\
|
|
55
|
+
"(#{human(@downloaded)}/#{human(@total)})"
|
|
56
|
+
else
|
|
57
|
+
print "\r#{human(@downloaded)} downloaded"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
$stdout.flush
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
def theme
|
|
66
|
+
STYLE[@options[:style]] || STYLE[:box]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def human(bytes)
|
|
70
|
+
if bytes > 1_048_576
|
|
71
|
+
format("%.2f MiB", bytes / 1_048_576.0)
|
|
72
|
+
elsif bytes > 1024
|
|
73
|
+
format("%.2f KiB", bytes / 1024.0)
|
|
74
|
+
else
|
|
75
|
+
"#{bytes} B"
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
data/lib/yobi/ui.rb
ADDED
data/lib/yobi/version.rb
CHANGED
data/lib/yobi.rb
CHANGED
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.12.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Vinciguerra
|
|
8
8
|
bindir: exe
|
|
9
9
|
cert_chain: []
|
|
10
|
-
date: 2026-02-
|
|
10
|
+
date: 2026-02-24 00:00:00.000000000 Z
|
|
11
11
|
dependencies:
|
|
12
12
|
- !ruby/object:Gem::Dependency
|
|
13
13
|
name: base64
|
|
@@ -50,6 +50,7 @@ files:
|
|
|
50
50
|
- Agents.md
|
|
51
51
|
- CHANGELOG.md
|
|
52
52
|
- CODE_OF_CONDUCT.md
|
|
53
|
+
- Examples.md
|
|
53
54
|
- README.md
|
|
54
55
|
- Rakefile
|
|
55
56
|
- exe/yobi
|
|
@@ -61,6 +62,11 @@ files:
|
|
|
61
62
|
- lib/yobi/extensions/net_http.rb
|
|
62
63
|
- lib/yobi/extensions/net_http/parsing.rb
|
|
63
64
|
- lib/yobi/http.rb
|
|
65
|
+
- lib/yobi/renders.rb
|
|
66
|
+
- lib/yobi/renders/colored.rb
|
|
67
|
+
- lib/yobi/renders/raw.rb
|
|
68
|
+
- lib/yobi/ui.rb
|
|
69
|
+
- lib/yobi/ui/progress.rb
|
|
64
70
|
- lib/yobi/version.rb
|
|
65
71
|
- screenshot.png
|
|
66
72
|
- spec/extensions/net_http/parsing_spec.rb
|