odysseus-cli 0.2.0 → 0.9.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.
@@ -1,156 +0,0 @@
1
- # odysseus-cli/lib/odysseus/cli/gum.rb
2
- # Wrapper for Charm's gum CLI tool
3
- # https://github.com/charmbracelet/gum
4
-
5
- require 'open3'
6
- require 'tempfile'
7
-
8
- module Odysseus
9
- module CLI
10
- module Gum
11
- class << self
12
- # Check if gum is installed and available
13
- def available?
14
- @available ||= system('which gum > /dev/null 2>&1')
15
- end
16
-
17
- # Display a spinner while executing a block
18
- # Returns the block's result
19
- def spin(title:, spinner: 'dot')
20
- return yield unless available?
21
-
22
- result = nil
23
- error = nil
24
-
25
- # We can't use gum spin directly with Ruby blocks, so we show spinner
26
- # and run the block in a thread
27
- spin_pid = spawn("gum spin --spinner #{spinner} --title #{shell_escape(title)} -- sleep infinity",
28
- out: '/dev/null', err: '/dev/null')
29
-
30
- begin
31
- result = yield
32
- rescue => e
33
- error = e
34
- ensure
35
- Process.kill('TERM', spin_pid) rescue nil
36
- Process.wait(spin_pid) rescue nil
37
- end
38
-
39
- raise error if error
40
- result
41
- end
42
-
43
- # Interactive selection menu
44
- # Returns selected option or nil if cancelled
45
- def choose(options, header: nil)
46
- return nil unless available?
47
-
48
- args = ['gum', 'choose']
49
- args += ['--header', header] if header
50
- args += options
51
-
52
- stdout, status = Open3.capture2(*args)
53
- return nil unless status.success?
54
-
55
- stdout.strip
56
- end
57
-
58
- # Yes/No confirmation dialog
59
- # Returns true for yes, false for no
60
- def confirm(message)
61
- return true unless available?
62
-
63
- system('gum', 'confirm', message)
64
- end
65
-
66
- # Style text with borders, colors, padding
67
- def style(text, border: nil, foreground: nil, background: nil, padding: nil, margin: nil, bold: false)
68
- return text unless available?
69
-
70
- args = ['gum', 'style']
71
- args += ['--border', border] if border
72
- args += ['--foreground', foreground.to_s] if foreground
73
- args += ['--background', background.to_s] if background
74
- args += ['--padding', padding.to_s] if padding
75
- args += ['--margin', margin.to_s] if margin
76
- args << '--bold' if bold
77
- args << text
78
-
79
- stdout, status = Open3.capture2(*args)
80
- status.success? ? stdout : text
81
- end
82
-
83
- # Display a table from headers and rows
84
- # Returns formatted table string
85
- def table(headers:, rows:)
86
- return simple_table(headers, rows) unless available?
87
-
88
- # gum table reads CSV from stdin
89
- csv_data = [headers.join(',')]
90
- rows.each do |row|
91
- csv_data << row.map { |cell| csv_escape(cell.to_s) }.join(',')
92
- end
93
-
94
- stdout, status = Open3.capture2('gum', 'table', stdin_data: csv_data.join("\n"))
95
- status.success? ? stdout : simple_table(headers, rows)
96
- end
97
-
98
- # Format text (markdown, code, etc.)
99
- def format(text, type: 'markdown')
100
- return text unless available?
101
-
102
- stdout, status = Open3.capture2('gum', 'format', '-t', type, stdin_data: text)
103
- status.success? ? stdout : text
104
- end
105
-
106
- # Display a log message with level styling
107
- def log(message, level: 'info')
108
- return puts(message) unless available?
109
-
110
- system('gum', 'log', '-l', level, message)
111
- end
112
-
113
- # Join multiple styled blocks horizontally or vertically
114
- def join(*texts, horizontal: false)
115
- return texts.join("\n") unless available?
116
-
117
- args = ['gum', 'join']
118
- args << '--horizontal' if horizontal
119
- args += texts
120
-
121
- stdout, status = Open3.capture2(*args)
122
- status.success? ? stdout : texts.join(horizontal ? ' ' : "\n")
123
- end
124
-
125
- private
126
-
127
- def shell_escape(str)
128
- "'#{str.gsub("'", "'\\\\''")}'"
129
- end
130
-
131
- def csv_escape(str)
132
- if str.include?(',') || str.include?('"') || str.include?("\n")
133
- "\"#{str.gsub('"', '""')}\""
134
- else
135
- str
136
- end
137
- end
138
-
139
- # Fallback simple table for when gum is not available
140
- def simple_table(headers, rows)
141
- widths = headers.map.with_index do |h, i|
142
- [h.to_s.length, rows.map { |r| r[i].to_s.length }.max || 0].max
143
- end
144
-
145
- lines = []
146
- lines << headers.map.with_index { |h, i| h.to_s.ljust(widths[i]) }.join(' ')
147
- lines << widths.map { |w| '-' * w }.join(' ')
148
- rows.each do |row|
149
- lines << row.map.with_index { |c, i| c.to_s.ljust(widths[i]) }.join(' ')
150
- end
151
- lines.join("\n")
152
- end
153
- end
154
- end
155
- end
156
- end