gum 0.3.0-arm64-linux

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 04da5a10876b453c8c3e8cc911d083d547ab25f3187d646ecc1cd823ef0087f8
4
+ data.tar.gz: 1dcfedba5197b25c353a796d2123c65f8a882cf54bc2714b0cad7801b3886383
5
+ SHA512:
6
+ metadata.gz: c29db2d3e3aa86eba9dedbb1fd9fa1f5135985d86c0c1c84cb81f172f617d03ebb9d464fe087b23af62b0ea53086375f6e561c6e8ad6ac4e7a058987334f26f6
7
+ data.tar.gz: 42c13e04922d19ea5dfdeceafa84fbee8debf2c01a98b0fb5aae96a981400026fc795916399ba4acf6d986360fac552ae4367fc829b9f64ca257b96a384b7695
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2025 Marco Roth
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,460 @@
1
+ # Gum for Ruby
2
+
3
+ <p>
4
+ <a href="https://github.com/marcoroth/gum-ruby" target="_blank"><img src="assets/logo.png" alt="Gum Image" width="450" /></a>
5
+ </p>
6
+
7
+ Ruby wrapper for [Charm's Gum](https://github.com/charmbracelet/gum). A tool for glamorous scripts.
8
+
9
+ This gem bundles the `gum` binary and provides an idiomatic Ruby API for all gum commands.
10
+
11
+ ## Installation
12
+
13
+ **Add to your Gemfile:**
14
+
15
+ ```bash
16
+ bundle add gum
17
+ ```
18
+
19
+ **Or install directly:**
20
+
21
+ ```bash
22
+ gem install gum
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ ```ruby
28
+ require "gum"
29
+ ```
30
+
31
+ ### Input
32
+
33
+ **Prompt for single-line input:**
34
+
35
+ ```ruby
36
+ name = Gum.input(placeholder: "Enter your name")
37
+ ```
38
+
39
+ **Password input (masked):**
40
+
41
+ ```ruby
42
+ password = Gum.input(password: true)
43
+ ```
44
+
45
+ **With default value and custom prompt:**
46
+
47
+ ```ruby
48
+ email = Gum.input(value: "user@", prompt: "> ", placeholder: "email")
49
+ ```
50
+
51
+ **With character limit:**
52
+
53
+ ```ruby
54
+ code = Gum.input(placeholder: "Enter code", char_limit: 6)
55
+ ```
56
+
57
+ ### Write
58
+
59
+ **Prompt for multi-line text input (Ctrl+D to submit):**
60
+
61
+ ```ruby
62
+ description = Gum.write(placeholder: "Enter description...")
63
+ ```
64
+
65
+ **With dimensions:**
66
+
67
+ ```ruby
68
+ notes = Gum.write(width: 80, height: 10, header: "Notes")
69
+ ```
70
+
71
+ **With line numbers:**
72
+
73
+ ```ruby
74
+ content = Gum.write(show_line_numbers: true)
75
+ ```
76
+
77
+ ### Choose
78
+
79
+ **Single selection (array):**
80
+
81
+ ```ruby
82
+ color = Gum.choose(["red", "green", "blue"])
83
+ ```
84
+
85
+ **Single selection (splat):**
86
+
87
+ ```ruby
88
+ color = Gum.choose("red", "green", "blue")
89
+ ```
90
+
91
+ **Multiple selection with limit:**
92
+
93
+ ```ruby
94
+ colors = Gum.choose(["red", "green", "blue"], limit: 2)
95
+ ```
96
+
97
+ **Unlimited selection:**
98
+
99
+ ```ruby
100
+ colors = Gum.choose(["red", "green", "blue"], no_limit: true)
101
+ ```
102
+
103
+ **With header and custom height:**
104
+
105
+ ```ruby
106
+ choice = Gum.choose(options, header: "Pick one:", height: 10)
107
+ ```
108
+
109
+ **Pre-selected items:**
110
+
111
+ ```ruby
112
+ choice = Gum.choose(["a", "b", "c"], selected: ["b"])
113
+ ```
114
+
115
+ ### Filter
116
+
117
+ **Single selection (array):**
118
+
119
+ ```ruby
120
+ file = Gum.filter(Dir.glob("**/*.rb"))
121
+ ```
122
+
123
+ **Single selection (splat):**
124
+
125
+ ```ruby
126
+ file = Gum.filter("file1.rb", "file2.rb", "file3.rb")
127
+ ```
128
+
129
+ **Multiple selection:**
130
+
131
+ ```ruby
132
+ files = Gum.filter(Dir.glob("*"), limit: 5)
133
+ ```
134
+
135
+ **Unlimited selection:**
136
+
137
+ ```ruby
138
+ files = Gum.filter(items, no_limit: true)
139
+ ```
140
+
141
+ **With placeholder and height:**
142
+
143
+ ```ruby
144
+ selection = Gum.filter(items, placeholder: "Search...", height: 20)
145
+ ```
146
+
147
+ **Disable fuzzy matching for exact search:**
148
+
149
+ ```ruby
150
+ result = Gum.filter(items, fuzzy: false)
151
+ ```
152
+
153
+ ### Confirm
154
+
155
+ **Ask for yes/no confirmation:**
156
+
157
+ ```ruby
158
+ if Gum.confirm("Delete file?")
159
+ File.delete(path)
160
+ end
161
+ ```
162
+
163
+ **With default value:**
164
+
165
+ ```ruby
166
+ proceed = Gum.confirm("Continue?", default: true)
167
+ ```
168
+
169
+ **Custom button labels:**
170
+
171
+ ```ruby
172
+ Gum.confirm("Save changes?", affirmative: "Save", negative: "Discard")
173
+ ```
174
+
175
+ ### File
176
+
177
+ **Start from current directory:**
178
+
179
+ ```ruby
180
+ path = Gum.file
181
+ ```
182
+
183
+ **Start from specific directory:**
184
+
185
+ ```ruby
186
+ path = Gum.file("~/Documents")
187
+ ```
188
+
189
+ **Show hidden files:**
190
+
191
+ ```ruby
192
+ path = Gum.file(all: true)
193
+ ```
194
+
195
+ **Only show directories:**
196
+
197
+ ```ruby
198
+ dir = Gum.file(directory_only: true)
199
+ ```
200
+
201
+ ### Pager
202
+
203
+ **Scroll through content:**
204
+
205
+ ```ruby
206
+ Gum.pager(File.read("README.md"))
207
+ ```
208
+
209
+ **With line numbers:**
210
+
211
+ ```ruby
212
+ Gum.pager(content, show_line_numbers: true)
213
+ ```
214
+
215
+ **Soft wrap long lines:**
216
+
217
+ ```ruby
218
+ Gum.pager(content, soft_wrap: true)
219
+ ```
220
+
221
+ ### Spin
222
+
223
+ **With shell command:**
224
+
225
+ ```ruby
226
+ Gum.spin("Installing...", command: "npm install")
227
+ ```
228
+
229
+ **With Ruby block:**
230
+
231
+ ```ruby
232
+ result = Gum.spin("Processing...") do
233
+ expensive_computation
234
+ end
235
+ ```
236
+
237
+ **Custom spinner type:**
238
+
239
+ ```ruby
240
+ Gum.spin("Loading...", spinner: :dot, command: "sleep 5")
241
+ ```
242
+
243
+ Available spinner types: `:line`, `:dot`, `:minidot`, `:jump`, `:pulse`, `:points`, `:globe`, `:moon`, `:monkey`, `:meter`, `:hamburger`
244
+
245
+ ### Style
246
+
247
+ **Basic styling:**
248
+
249
+ ```ruby
250
+ styled = Gum.style("Hello", foreground: "212", bold: true)
251
+ ```
252
+
253
+ **With border:**
254
+
255
+ ```ruby
256
+ box = Gum.style("Content", border: :double, padding: "1 2")
257
+ ```
258
+
259
+ **Multiple lines with alignment:**
260
+
261
+ ```ruby
262
+ styled = Gum.style("Line 1", "Line 2", align: :center, width: 50)
263
+ ```
264
+
265
+ **Full styling example:**
266
+
267
+ ```ruby
268
+ styled = Gum.style(
269
+ "Bubble Gum",
270
+ foreground: "212",
271
+ border: :double,
272
+ border_foreground: "212",
273
+ align: :center,
274
+ width: 50,
275
+ margin: "1 2",
276
+ padding: "2 4"
277
+ )
278
+ ```
279
+
280
+ Available border types: `:none`, `:hidden`, `:rounded`, `:double`, `:thick`, `:normal`
281
+
282
+ ### Join
283
+
284
+ Join text blocks horizontally or vertically:
285
+
286
+ ```ruby
287
+ box1 = Gum.style("A", border: :rounded, padding: "1 3")
288
+ box2 = Gum.style("B", border: :rounded, padding: "1 3")
289
+ ```
290
+
291
+ **Horizontal join (default):**
292
+
293
+ ```ruby
294
+ combined = Gum.join(box1, box2)
295
+ ```
296
+
297
+ **Vertical join:**
298
+
299
+ ```ruby
300
+ stacked = Gum.join(box1, box2, vertical: true)
301
+ ```
302
+
303
+ **With alignment:**
304
+
305
+ ```ruby
306
+ aligned = Gum.join(box1, box2, vertical: true, align: :center)
307
+ ```
308
+
309
+ ### Format
310
+
311
+ **Markdown:**
312
+
313
+ ```ruby
314
+ Gum.format("# Hello\n- Item 1\n- Item 2", type: :markdown)
315
+ ```
316
+
317
+ **Template (see Termenv docs for helpers):**
318
+
319
+ ```ruby
320
+ Gum.format('{{ Bold "Hello" }} {{ Color "99" "0" " World " }}', type: :template)
321
+ ```
322
+
323
+ **Emoji:**
324
+
325
+ ```ruby
326
+ Gum.format("I :heart: Ruby :gem:", type: :emoji)
327
+ # => "I ❤️ Ruby 💎"
328
+ ```
329
+
330
+ **Shorthand methods:**
331
+
332
+ ```ruby
333
+ Gum::Format.markdown("# Hello")
334
+ Gum::Format.emoji("I :heart: Ruby")
335
+ ```
336
+
337
+ ### Table
338
+
339
+ **From array of arrays:**
340
+
341
+ ```ruby
342
+ data = [["Alice", "30"], ["Bob", "25"]]
343
+ selection = Gum.table(data, columns: %w[Name Age])
344
+ ```
345
+
346
+ **Just print (no selection):**
347
+
348
+ ```ruby
349
+ Gum.table(data, columns: %w[Name Age], print: true)
350
+ ```
351
+
352
+ **From CSV string:**
353
+
354
+ ```ruby
355
+ Gum.table(File.read("data.csv"))
356
+ ```
357
+
358
+ **With custom border:**
359
+
360
+ ```ruby
361
+ Gum.table(data, columns: %w[Name Age], border: :rounded)
362
+ ```
363
+
364
+ ### Log
365
+
366
+ **Basic logging:**
367
+
368
+ ```ruby
369
+ Gum.log("Application started", level: :info)
370
+ ```
371
+
372
+ **Structured logging with key-value pairs:**
373
+
374
+ ```ruby
375
+ Gum.log("User created", level: :info, user_id: 123, email: "user@example.com")
376
+ ```
377
+
378
+ **With timestamp:**
379
+
380
+ ```ruby
381
+ Gum.log("Error occurred", level: :error, time: :rfc822)
382
+ ```
383
+
384
+ **Shorthand methods:**
385
+
386
+ ```ruby
387
+ Gum::Log.debug("Debug message")
388
+ Gum::Log.info("Info message")
389
+ Gum::Log.warn("Warning message")
390
+ Gum::Log.error("Error message", code: 500)
391
+ ```
392
+
393
+ Available log levels: `:debug`, `:info`, `:warn`, `:error`, `:fatal`
394
+
395
+ ## Styling Options
396
+
397
+ Most commands support styling options as hashes. Common style properties include:
398
+
399
+ ```ruby
400
+ {
401
+ foreground: "212", # ANSI color code, hex (#ff0000), or name
402
+ background: "0",
403
+ bold: true,
404
+ italic: true,
405
+ underline: true,
406
+ strikethrough: true,
407
+ faint: true,
408
+ }
409
+ ```
410
+
411
+ **Example with input styling:**
412
+
413
+ ```ruby
414
+ Gum.input(
415
+ placeholder: "Enter name",
416
+ cursor: { foreground: "#FF0" },
417
+ prompt_style: { foreground: "#0FF" }
418
+ )
419
+ ```
420
+
421
+ ## Environment Variables
422
+
423
+ - `GUM_INSTALL_DIR` - Override the path to the gum binary
424
+
425
+ ## Raw Command Execution
426
+
427
+ For commands not covered by the Ruby API, you can execute gum directly:
428
+
429
+ ```ruby
430
+ Gum.execute("choose", "a", "b", "c", "--limit", "2")
431
+ ```
432
+
433
+ ## Development
434
+
435
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
436
+
437
+ ### Building Platform Gems
438
+
439
+ ```bash
440
+ rake gem:ruby # Build pure Ruby gem (no binary)
441
+ rake gem:arm64-darwin # Build macOS ARM64 gem
442
+ rake gem:x86_64-darwin # Build macOS Intel gem
443
+ rake gem:arm64-linux # Build Linux ARM64 gem
444
+ rake gem:x86_64-linux # Build Linux x86_64 gem
445
+ rake package # Build all platform gems
446
+ rake download # Download all binaries
447
+ ```
448
+
449
+ ## Contributing
450
+
451
+ Bug reports and pull requests are welcome on GitHub at https://github.com/marcoroth/gum-ruby.
452
+
453
+ ## License
454
+
455
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
456
+
457
+ ## Acknowledgements
458
+
459
+ - [Charm](https://charm.sh/) for creating the amazing [gum](https://github.com/charmbracelet/gum) tool
460
+ - Inspired by [`litestream-ruby`](https://github.com/fractaledmind/litestream-ruby) for the native binary packaging approach
Binary file
data/exe/gum ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Because rubygems shims assume a gem's executables are Ruby scripts,
5
+ # we need this wrapper to find and exec the native gum binary.
6
+
7
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
8
+
9
+ require "gum"
10
+
11
+ exec(Gum.executable, *ARGV)
data/gum.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/gum/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "gum"
7
+ spec.version = Gum::VERSION
8
+ spec.authors = ["Marco Roth"]
9
+ spec.email = ["marco.roth@intergga.ch"]
10
+
11
+ spec.summary = "Ruby wrapper for Charm's gum CLI tool."
12
+ spec.description = "Integrate Charm's gum with the RubyGems infrastructure."
13
+ spec.homepage = "https://github.com/marcoroth/gum-ruby"
14
+ spec.license = "MIT"
15
+ spec.required_ruby_version = ">= 3.2.0"
16
+
17
+ spec.metadata = {
18
+ "homepage_uri" => spec.homepage,
19
+ "source_code_uri" => "https://github.com/marcoroth/gum-ruby",
20
+ "changelog_uri" => "https://github.com/marcoroth/gum-ruby/releases",
21
+ "rubygems_mfa_required" => "true",
22
+ }
23
+
24
+ spec.files = Dir[
25
+ "gum.gemspec",
26
+ "lib/**/*",
27
+ "sig/**/*",
28
+ "LICENSE.txt",
29
+ "README.md"
30
+ ]
31
+
32
+ spec.bindir = "exe"
33
+ spec.executables = ["gum"]
34
+ spec.require_paths = ["lib"]
35
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+ # typed: true
3
+ # rbs_inline: enabled
4
+
5
+ require "English"
6
+ require "open3"
7
+
8
+ module Gum
9
+ module Command
10
+ def self.run(*, input: nil, interactive: true)
11
+ if input && interactive
12
+ run_interactive_with_input(*, input: input)
13
+ elsif input
14
+ run_non_interactive(*, input: input)
15
+ else
16
+ run_interactive(*)
17
+ end
18
+ end
19
+
20
+ def self.run_non_interactive(*args, input:)
21
+ stdout, stderr, status = Open3.capture3(Gum.executable, *args.map(&:to_s), stdin_data: input)
22
+
23
+ unless status.success?
24
+ return nil if status.exitstatus == 130 # User cancelled (Ctrl+C)
25
+
26
+ raise Error, "gum #{args.first} failed: #{stderr}" unless stderr.empty?
27
+ end
28
+
29
+ stdout.chomp
30
+ end
31
+
32
+ def self.run_interactive(*args)
33
+ tty = File.open("/dev/tty", "r+")
34
+
35
+ stdout, wait_thread = Open3.pipeline_r(
36
+ [Gum.executable, *args.map(&:to_s)],
37
+ in: tty,
38
+ err: tty
39
+ )
40
+
41
+ output = stdout.read.chomp
42
+ stdout.close
43
+ tty.close
44
+
45
+ status = wait_thread.last.value
46
+ return nil if status.exitstatus == 130 # User cancelled
47
+ return nil unless status.success?
48
+
49
+ output
50
+ rescue Errno::ENOENT, Errno::ENXIO, Errno::EIO
51
+ stdout, stderr, status = Open3.capture3(Gum.executable, *args.map(&:to_s))
52
+
53
+ unless status.success?
54
+ return nil if status.exitstatus == 130
55
+ raise Error, "gum #{args.first} failed: #{stderr}" unless stderr.empty?
56
+ end
57
+
58
+ stdout.chomp
59
+ end
60
+
61
+ def self.run_interactive_with_input(*args, input:)
62
+ tty = File.open("/dev/tty", "r+")
63
+ stdin_read, stdin_write = IO.pipe
64
+ stdout_read, stdout_write = IO.pipe
65
+
66
+ pid = Process.spawn(
67
+ Gum.executable, *args.map(&:to_s),
68
+ in: stdin_read,
69
+ out: stdout_write,
70
+ err: tty
71
+ )
72
+
73
+ stdin_read.close
74
+ stdout_write.close
75
+
76
+ stdin_write.write(input)
77
+ stdin_write.close
78
+
79
+ output = stdout_read.read.chomp
80
+ stdout_read.close
81
+
82
+ _, status = Process.wait2(pid)
83
+ tty.close
84
+
85
+ return nil if status.exitstatus == 130 # User cancelled
86
+ return nil unless status.success?
87
+
88
+ output
89
+ rescue Errno::ENOENT, Errno::ENXIO, Errno::EIO
90
+ run_non_interactive(*args, input: input)
91
+ end
92
+
93
+ def self.run_display_only(*args, input:)
94
+ IO.popen([Gum.executable, *args.map(&:to_s)], "w") do |io|
95
+ io.write(input)
96
+ end
97
+
98
+ $CHILD_STATUS.success? || nil
99
+ rescue Errno::ENOENT
100
+ raise Error, "gum executable not found"
101
+ end
102
+
103
+ def self.run_with_status(*args, input: nil)
104
+ if input
105
+ _stdout, _stderr, status = Open3.capture3(Gum.executable, *args.map(&:to_s), stdin_data: input)
106
+ status.success?
107
+ else
108
+ system(Gum.executable, *args.map(&:to_s))
109
+ end
110
+ end
111
+
112
+ def self.build_args(command, *positional, **options)
113
+ args = [command]
114
+ args.concat(positional.flatten.compact)
115
+
116
+ options.each do |key, value|
117
+ next if value.nil?
118
+
119
+ flag = key.to_s.tr("_", "-")
120
+
121
+ case value
122
+ when true
123
+ args << "--#{flag}"
124
+ when false
125
+ args << "--no-#{flag}" if flag_supports_negation?(command, flag)
126
+ when Array
127
+ value.each { |v| args << "--#{flag}=#{v}" }
128
+ when Hash
129
+ value.each { |k, v| args << "--#{flag}.#{k}=#{v}" }
130
+ else
131
+ args << "--#{flag}=#{value}"
132
+ end
133
+ end
134
+
135
+ args
136
+ end
137
+
138
+ def self.add_style_args(args, flag, style_hash)
139
+ return unless style_hash
140
+
141
+ style_hash.each do |key, value|
142
+ args << "--#{flag}.#{key}" << value.to_s
143
+ end
144
+ end
145
+
146
+ def self.flag_supports_negation?(command, flag)
147
+ negatable = {
148
+ "filter" => ["fuzzy", "sort", "strict", "reverse", "indicator"],
149
+ "choose" => ["limit"],
150
+ "input" => ["echo-mode"],
151
+ "file" => ["all", "file", "directory"],
152
+ "pager" => ["soft-wrap"],
153
+ "table" => ["border", "print"],
154
+ }
155
+
156
+ negatable.fetch(command, []).include?(flag)
157
+ end
158
+ end
159
+ end