dry-cli-ui 0.3.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 412754e986acfa5b60ce22a1d447cd470098ffe42434ec453636cf563e65a8d3
4
- data.tar.gz: 23fcbd8f9cbd0cdda55e6f88883b36092b07703e3895f47f8348a11743f4f3a8
3
+ metadata.gz: 555aba61a68d2f480ae76f61d6851786e83e46afd0855c276e5eba01fe0d8dc8
4
+ data.tar.gz: 584e4a3268d121ecdb3e85f3ead9afef643e5c1a086b0c4702b418f550c1b8d7
5
5
  SHA512:
6
- metadata.gz: 46f47189bba69663ef98407036a101983ff870f8daa56e02fcbd788bf4a82f83d9a8546730c2cfc61056548a26ccc84102fe2f1a64d70ff1a063d5517c8179fe
7
- data.tar.gz: b83ec6dd332065f8fdc8cb346abe35773e5ddb3cc14a01cec09dd7eabcba04529194e003d837336c1891d21f140b2e344d8ef1f0fedab942b21facd4c1634b96
6
+ metadata.gz: 86e23d86e7eca50b4a7ed51bf7dc6d4a784c4711f7ddfc5c10fbc90ebc53d44647da0eaea7ea00296e7228ba73c08920a1dd08ff6c435cbd4d10f2b3266d4c37
7
+ data.tar.gz: 3b73f7e064fd7dfd35540a034ef1e721e4aed3e368d4d474ffa8ccc283fd4c06d4913bba72343b3760e8ecaf9ec8a64645c4d97f6c160bfb26cf7c7cb5021f30
data/examples/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ group :development, :test do
6
+ gem 'dry-cli'
7
+ gem 'dry-cli-autocomplete'
8
+ gem 'dry-cli-help'
9
+ gem 'prime'
10
+ # multi_spinner, multi_progress and status_bar live on the
11
+ # kig/add-multi-spinner-progress branch until it is merged; switch this
12
+ # back to path: "../" then.
13
+ gem 'dry-cli-ui', path: File.expand_path("~/.agents.worktrees/dry-cli-ui-add-multi-spinner-progress")
14
+ end
@@ -0,0 +1,109 @@
1
+ PATH
2
+ remote: /Users/kig/.agents.worktrees/dry-cli-ui-add-multi-spinner-progress
3
+ specs:
4
+ dry-cli-ui (0.3.0)
5
+ concurrent-ruby (~> 1.3)
6
+ dry-cli (>= 1.0)
7
+ pastel (~> 0.8)
8
+ strings (~> 0.2)
9
+ tty-box (~> 0.7)
10
+ tty-cursor (~> 0.7)
11
+ tty-progressbar (~> 0.18)
12
+ tty-prompt (~> 0.23)
13
+ tty-screen (~> 0.8)
14
+ tty-spinner (~> 0.9)
15
+ tty-table (~> 0.12)
16
+
17
+ GEM
18
+ remote: https://rubygems.org/
19
+ specs:
20
+ concurrent-ruby (1.3.8)
21
+ dry-cli (1.4.1)
22
+ dry-cli-autocomplete (0.1.3)
23
+ dry-cli (>= 1.0)
24
+ dry-inflector (>= 1.0)
25
+ dry-cli-help (0.2.0)
26
+ dry-cli (>= 1.1.1, < 2)
27
+ pastel (~> 0.8)
28
+ dry-inflector (1.3.1)
29
+ forwardable (1.4.0)
30
+ pastel (0.8.0)
31
+ tty-color (~> 0.5)
32
+ prime (0.1.4)
33
+ forwardable
34
+ singleton
35
+ singleton (0.3.0)
36
+ strings (0.2.1)
37
+ strings-ansi (~> 0.2)
38
+ unicode-display_width (>= 1.5, < 3.0)
39
+ unicode_utils (~> 1.4)
40
+ strings-ansi (0.2.0)
41
+ tty-box (0.7.0)
42
+ pastel (~> 0.8)
43
+ strings (~> 0.2.0)
44
+ tty-cursor (~> 0.7)
45
+ tty-color (0.6.0)
46
+ tty-cursor (0.7.1)
47
+ tty-progressbar (0.18.3)
48
+ strings-ansi (~> 0.2)
49
+ tty-cursor (~> 0.7)
50
+ tty-screen (~> 0.8)
51
+ unicode-display_width (>= 1.6, < 3.0)
52
+ tty-prompt (0.23.1)
53
+ pastel (~> 0.8)
54
+ tty-reader (~> 0.8)
55
+ tty-reader (0.9.0)
56
+ tty-cursor (~> 0.7)
57
+ tty-screen (~> 0.8)
58
+ wisper (~> 2.0)
59
+ tty-screen (0.8.2)
60
+ tty-spinner (0.9.3)
61
+ tty-cursor (~> 0.7)
62
+ tty-table (0.12.0)
63
+ pastel (~> 0.8)
64
+ strings (~> 0.2.0)
65
+ tty-screen (~> 0.8)
66
+ unicode-display_width (2.6.0)
67
+ unicode_utils (1.4.0)
68
+ wisper (2.0.1)
69
+
70
+ PLATFORMS
71
+ arm64-darwin-25
72
+ ruby
73
+
74
+ DEPENDENCIES
75
+ dry-cli
76
+ dry-cli-autocomplete
77
+ dry-cli-help
78
+ dry-cli-ui!
79
+ prime
80
+
81
+ CHECKSUMS
82
+ bundler (4.1.0.beta1) sha256=8a2dac8ca276ff4eff5efc2c6457b0044080fc1e431534ad7c5cc6187232b800
83
+ concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
84
+ dry-cli (1.4.1) sha256=b8015bb76c708aa8705a36faf694973e75eeeffca39b89c8e172dc6f66a7d874
85
+ dry-cli-autocomplete (0.1.3) sha256=14f76447bda0000c71739b585c827dc0a0a763e30a68a8b4b2dff91f640768e4
86
+ dry-cli-help (0.2.0)
87
+ dry-cli-ui (0.3.0)
88
+ dry-inflector (1.3.1) sha256=7fb0c2bb04f67638f25c52e7ba39ab435d922a3a5c3cd196120f63accb682dcc
89
+ forwardable (1.4.0) sha256=f1cd40cc9812937980e1c76f1aa053660990a7c9b6a98fc37d945468afcce838
90
+ pastel (0.8.0) sha256=481da9fb7d2f6e6b1a08faf11fa10363172dc40fd47848f096ae21209f805a75
91
+ prime (0.1.4) sha256=4d755ebf7c2994a6f3a3fee0d072063be3fff2d4042ebff6cd5eebd4747a225e
92
+ singleton (0.3.0) sha256=83ea1bca5f4aa34d00305ab842a7862ea5a8a11c73d362cb52379d94e9615778
93
+ strings (0.2.1) sha256=933293b3c95cf85b81eb44b3cf673e3087661ba739bbadfeadf442083158d6fb
94
+ strings-ansi (0.2.0) sha256=90262d760ea4a94cc2ae8d58205277a343409c288cbe7c29416b1826bd511c88
95
+ tty-box (0.7.0) sha256=17f453a0c1df43871fdb6de461ee797b78574dacc652372fa2ae98a00785df8b
96
+ tty-color (0.6.0) sha256=6f9c37ca3a4e2367fb2e6d09722762647d6f455c111f05b59f35730eeb24332a
97
+ tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48
98
+ tty-progressbar (0.18.3) sha256=6cbb4260e55e74a9180d502143eb6a467d2c8e51bf5f3c9509fb5cacc3d4a5f6
99
+ tty-prompt (0.23.1) sha256=fcdbce905238993f27eecfdf67597a636bc839d92192f6a0eef22b8166449ec8
100
+ tty-reader (0.9.0) sha256=c62972c985c0b1566f0e56743b6a7882f979d3dc32ff491ed490a076f899c2b1
101
+ tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50
102
+ tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542
103
+ tty-table (0.12.0) sha256=fdc27a4750835c1a16efe19a0b857e3ced3652cc7aceafe6dca94908965b9939
104
+ unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a
105
+ unicode_utils (1.4.0) sha256=b922d0cf2313b6b7136ada6645ce7154ffc86418ca07d53b058efe9eb72f2a40
106
+ wisper (2.0.1) sha256=ce17bc5c3a166f241a2e6613848b025c8146fce2defba505920c1d1f3f88fae6
107
+
108
+ BUNDLED WITH
109
+ 4.1.0.beta1
@@ -0,0 +1,35 @@
1
+ # Examples
2
+
3
+ These examples offers two commands that are are indicative of the added behavior.
4
+
5
+ But first,
6
+
7
+ ```bash
8
+ bundle install
9
+ ```
10
+
11
+ Then:
12
+
13
+ ## Help
14
+
15
+ ```bash
16
+ $ bundle exec bin/mycli -h
17
+ USAGE
18
+ mycli COMMAND [OPTIONS]
19
+
20
+ COMMANDS
21
+ version, v Print version
22
+ urls_progress Fetch URLs with a progress bar each, and write them all to urls.txt
23
+ urls_spinner Fetch URLs with a spinner each, and write each to its own file
24
+ primes Compute closest prime to a given number in the array
25
+
26
+ OPTIONS
27
+ -h, --help Show help
28
+ -v, --version Print version
29
+ ```
30
+
31
+ ## Commands to Try
32
+
33
+ - primes
34
+ - urls_progress url1 url2 ...
35
+ - urls_spinner url1 url2 ...
@@ -0,0 +1,206 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Load the gems examples/Gemfile names, even when run as bin/mycli without
5
+ # bundle exec; otherwise Ruby picks the newest installed dry-cli-ui instead.
6
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__)
7
+ require "bundler/setup"
8
+
9
+ require "fileutils"
10
+ require "dry/cli"
11
+ require "dry/cli/help"
12
+ require "dry/cli/autocomplete/command"
13
+ require "dry/cli/ui"
14
+ require "net/http"
15
+ require "prime"
16
+
17
+ module Foo
18
+ module CLI
19
+ module Commands
20
+ extend Dry::CLI::Registry
21
+
22
+ class Version < Dry::CLI::Command
23
+ desc "Print version"
24
+
25
+ def call(*)
26
+ puts "1.0.0"
27
+ end
28
+ end
29
+
30
+ # Shared by the two URL commands: follows redirects, and asks for the
31
+ # body as it is stored, so the bytes counted match Content-Length.
32
+ module HTTP
33
+ HEADERS = { "Accept-Encoding" => "identity" }.freeze
34
+ REDIRECTS = 5
35
+
36
+ module_function
37
+
38
+ # @return [Array(URI, Integer, nil)] where the URL ends up, and its size when the server says
39
+ # @raise [RuntimeError] for a response that is neither a redirect nor a success
40
+ def resolve(url, hops = REDIRECTS)
41
+ uri = URI(url)
42
+ response = start(uri) { |http| http.head(uri.request_uri, HEADERS) }
43
+ if response.is_a?(Net::HTTPRedirection) && hops.positive?
44
+ return resolve(URI.join(uri, response["location"]).to_s, hops - 1)
45
+ end
46
+ raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
47
+
48
+ [uri, response.content_length]
49
+ end
50
+
51
+ # @yieldparam bytes [Integer] the size of each chunk as it arrives
52
+ # @return [String] the body
53
+ # @raise [RuntimeError] for a response that is not a success
54
+ def download(uri)
55
+ body = +""
56
+ start(uri) do |http|
57
+ http.request_get(uri.request_uri, HEADERS) do |response|
58
+ raise "HTTP #{response.code}" unless response.is_a?(Net::HTTPSuccess)
59
+
60
+ response.read_body do |chunk|
61
+ body << chunk
62
+ yield chunk.bytesize if block_given?
63
+ end
64
+ end
65
+ end
66
+ body
67
+ end
68
+
69
+ def start(uri, &)
70
+ Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == "https", open_timeout: 5, read_timeout: 10, &)
71
+ end
72
+ end
73
+
74
+ # One progress bar per URL, filling as bytes arrive, under a headline
75
+ # bar that counts every byte.
76
+ class FetchUrlsProgress < Dry::CLI::Command
77
+ include Dry::CLI::UI
78
+
79
+ desc "Fetch URLs with a progress bar each, and write them all to urls.txt"
80
+ argument :urls, type: :array, required: true, desc: "URLs to fetch, separated by spaces"
81
+
82
+ def call(urls:, **)
83
+ # A bar's total is fixed when it is declared, so ask each URL for its
84
+ # size first, with a HEAD request. A URL that fails here is left out.
85
+ found = urls.filter_map do |url|
86
+ [url, *HTTP.resolve(url)]
87
+ rescue StandardError => e
88
+ ui.status "#{url}: #{e.message}", level: :warn
89
+ nil
90
+ end
91
+ return ui.error("None of the #{urls.size} URLs could be fetched") if found.empty?
92
+
93
+ bodies = ui.multi_progress("Fetching #{found.size} URLs", concurrent: 8) do |m|
94
+ found.each do |url, uri, size|
95
+ m.progress(url, total: size || 1) do |bar|
96
+ sleep rand(1..4)
97
+ # Move the bar one byte at a time, as each chunk arrives.
98
+ body = HTTP.download(uri) { |bytes| bytes.times { bar.advance } if size }
99
+ bar.advance unless size # no Content-Length: done in one step
100
+ body
101
+ end
102
+ end
103
+ end
104
+
105
+ File.write("urls.txt", bodies.join("\n"))
106
+ ui.success "Wrote #{bodies.sum(&:bytesize)} bytes from #{found.size} of #{urls.size} URLs to urls.txt"
107
+ rescue StandardError => e
108
+ ui.error("Fetching failed", e.message)
109
+ end
110
+ end
111
+
112
+ # One spinner per URL, each saying what it is doing, and one file per URL.
113
+ class FetchUrls < Dry::CLI::Command
114
+ include Dry::CLI::UI
115
+
116
+ desc "Fetch URLs with a spinner each, and write each to its own file"
117
+ argument :urls, type: :array, required: true, desc: "URLs to fetch, separated by spaces"
118
+
119
+ def call(urls:, **)
120
+ bodies = ui.multi_spinner("Fetching #{urls.size} URLs", concurrent: 8) do |m|
121
+ urls.each do |url|
122
+ m.spinner(url) do |line|
123
+ line.detail = "resolving"
124
+ uri, = HTTP.resolve(url)
125
+ line.detail = "downloading #{uri.host}"
126
+ sleep rand(1..4) # slow enough to watch
127
+ HTTP.download(uri)
128
+ rescue StandardError => e
129
+ line.fail(e.message)
130
+ nil
131
+ end
132
+ end
133
+ end
134
+
135
+ files = urls.zip(bodies).filter_map do |url, body|
136
+ next unless body
137
+
138
+ "#{friendly_filename(url)}.txt".tap { |file| File.write(file, body) }
139
+ end
140
+ ui.success "Wrote #{files.size} of #{urls.size} URLs", files.join("\n")
141
+ end
142
+
143
+ private
144
+
145
+ def friendly_filename(url)
146
+ url.sub(%r{\Ahttps?://}, "").gsub(/[^\w.-]+/, "_").delete_suffix("_")
147
+ end
148
+ end
149
+
150
+ class ComputePrimes < Dry::CLI::Command
151
+ include Dry::CLI::UI
152
+
153
+ def self.closest_prime(n)
154
+ # Primes must be greater than 1
155
+ return 2 if n <= 2
156
+ return n if ::Prime.prime?(n)
157
+
158
+ distance = 1
159
+ loop do
160
+ lower = n - distance
161
+ upper = n + distance
162
+
163
+ primes = []
164
+ primes << lower if lower > 1 && ::Prime.prime?(lower)
165
+ primes << upper if ::Prime.prime?(upper)
166
+
167
+ # Return the prime(s) found at the shortest distance
168
+ return primes.size == 1 ? primes.first : primes if primes.any?
169
+
170
+ distance += 1
171
+ end
172
+ end
173
+
174
+ desc 'Compute closest prime to a given number in the array'
175
+ option :max, default: 100_000, desc: "Maximum integer to compute closest primes for", type: :integer
176
+
177
+ def call(max:)
178
+ ui.info "Computing closest primes..."
179
+ File.open('primes.txt', 'w') do |f|
180
+ integers = (1..max.to_i)
181
+ ui.progress("Computing Primes", total: integers.size) do |bar|
182
+ integers.each do |i|
183
+ p = ComputePrimes.closest_prime(i)
184
+ f.puts "closest prime to #{i} is #{p}"
185
+ bar.advance
186
+ end
187
+ end
188
+ end
189
+ ui.success("Computed #{max} primes, they are in the 'primes.txt' files.")
190
+ rescue StandardError => e
191
+ ui.error("Import failed", e.message)
192
+ end
193
+ end
194
+
195
+ register "version", Version, aliases: ["v", "-v", "--version"]
196
+ register "urls_progress", FetchUrlsProgress
197
+ register "urls_spinner", FetchUrls
198
+ register "completion", ::Dry::CLI::Autocomplete::Command[::Foo::CLI::Commands], hidden: true
199
+ register "primes", ComputePrimes
200
+ end
201
+ end
202
+ end
203
+
204
+ Dry::CLI.new(Foo::CLI::Commands).call
205
+
206
+ FileUtils.rm_f(Dir.glob("#{File.expand_path('../', __dir__)}/*.txt"))
@@ -11,7 +11,7 @@ module Dry
11
11
  # Presentation helpers for Dry::CLI commands.
12
12
  module UI
13
13
  # The gem version.
14
- VERSION = "0.3.0"
14
+ VERSION = "0.3.1"
15
15
  end
16
16
  end
17
17
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-cli-ui
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Konstantin Gredeskoul
@@ -177,6 +177,10 @@ files:
177
177
  - LICENSE.txt
178
178
  - README.md
179
179
  - SPECIFICATION.md
180
+ - examples/Gemfile
181
+ - examples/Gemfile.lock
182
+ - examples/README.md
183
+ - examples/bin/mycli
180
184
  - lib/dry-cli-ui.rb
181
185
  - lib/dry/cli/ui.rb
182
186
  - lib/dry/cli/ui/console.rb