beniya 0.1.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 +7 -0
- data/README.md +288 -0
- data/README_EN.md +289 -0
- data/beniya.gemspec +40 -0
- data/bin/beniya +31 -0
- data/config_example.rb +88 -0
- data/lib/beniya/application.rb +28 -0
- data/lib/beniya/color_helper.rb +150 -0
- data/lib/beniya/config.rb +179 -0
- data/lib/beniya/config_loader.rb +110 -0
- data/lib/beniya/directory_listing.rb +99 -0
- data/lib/beniya/file_opener.rb +114 -0
- data/lib/beniya/file_preview.rb +172 -0
- data/lib/beniya/health_checker.rb +224 -0
- data/lib/beniya/keybind_handler.rb +332 -0
- data/lib/beniya/terminal_ui.rb +469 -0
- data/lib/beniya/version.rb +5 -0
- data/lib/beniya.rb +17 -0
- metadata +161 -0
@@ -0,0 +1,469 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'io/console'
|
4
|
+
|
5
|
+
module Beniya
|
6
|
+
class TerminalUI
|
7
|
+
def initialize
|
8
|
+
console = IO.console
|
9
|
+
if console
|
10
|
+
@screen_width, @screen_height = console.winsize.reverse
|
11
|
+
else
|
12
|
+
# fallback values (for test environments etc.)
|
13
|
+
@screen_width = 80
|
14
|
+
@screen_height = 24
|
15
|
+
end
|
16
|
+
@running = false
|
17
|
+
end
|
18
|
+
|
19
|
+
def start(directory_listing, keybind_handler, file_preview)
|
20
|
+
@directory_listing = directory_listing
|
21
|
+
@keybind_handler = keybind_handler
|
22
|
+
@file_preview = file_preview
|
23
|
+
@keybind_handler.set_directory_listing(@directory_listing)
|
24
|
+
|
25
|
+
@running = true
|
26
|
+
setup_terminal
|
27
|
+
|
28
|
+
begin
|
29
|
+
main_loop
|
30
|
+
ensure
|
31
|
+
cleanup_terminal
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def setup_terminal
|
38
|
+
# terminal setup
|
39
|
+
system('tput smcup') # alternate screen
|
40
|
+
system('tput civis') # cursor invisible
|
41
|
+
print "\e[2J\e[H" # clear screen, cursor to home (first time only)
|
42
|
+
|
43
|
+
# re-acquire terminal size (just in case)
|
44
|
+
console = IO.console
|
45
|
+
return unless console
|
46
|
+
|
47
|
+
@screen_width, @screen_height = console.winsize.reverse
|
48
|
+
end
|
49
|
+
|
50
|
+
def cleanup_terminal
|
51
|
+
system('tput rmcup') # normal screen
|
52
|
+
system('tput cnorm') # cursor normal
|
53
|
+
puts ConfigLoader.message('app.terminated')
|
54
|
+
end
|
55
|
+
|
56
|
+
def main_loop
|
57
|
+
while @running
|
58
|
+
draw_screen
|
59
|
+
handle_input
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def draw_screen
|
64
|
+
# move cursor to top of screen (don't clear)
|
65
|
+
print "\e[H"
|
66
|
+
|
67
|
+
# header
|
68
|
+
draw_header
|
69
|
+
|
70
|
+
# main content (left: directory list, right: preview)
|
71
|
+
entries = get_display_entries
|
72
|
+
selected_entry = entries[@keybind_handler.current_index]
|
73
|
+
|
74
|
+
# calculate height with footer margin
|
75
|
+
content_height = @screen_height - 3 # ヘッダーとフッター分を除く
|
76
|
+
left_width = @screen_width / 2
|
77
|
+
right_width = @screen_width - left_width
|
78
|
+
|
79
|
+
# adjust so right panel doesn't overflow into left panel
|
80
|
+
right_width = @screen_width - left_width if left_width + right_width > @screen_width
|
81
|
+
|
82
|
+
draw_directory_list(entries, left_width, content_height)
|
83
|
+
draw_file_preview(selected_entry, right_width, content_height, left_width)
|
84
|
+
|
85
|
+
# footer
|
86
|
+
draw_footer
|
87
|
+
|
88
|
+
# move cursor to invisible position
|
89
|
+
print "\e[#{@screen_height};#{@screen_width}H"
|
90
|
+
end
|
91
|
+
|
92
|
+
def draw_header
|
93
|
+
current_path = @directory_listing.current_path
|
94
|
+
header = "📁 beniya - #{current_path}"
|
95
|
+
|
96
|
+
# Add filter indicator if in filter mode
|
97
|
+
if @keybind_handler.filter_active?
|
98
|
+
filter_text = " [Filter: #{@keybind_handler.filter_query}]"
|
99
|
+
header += filter_text
|
100
|
+
end
|
101
|
+
|
102
|
+
# abbreviate if path is too long
|
103
|
+
if header.length > @screen_width - 2
|
104
|
+
if @keybind_handler.filter_active?
|
105
|
+
# prioritize showing filter when active
|
106
|
+
filter_text = " [Filter: #{@keybind_handler.filter_query}]"
|
107
|
+
base_length = @screen_width - filter_text.length - 15
|
108
|
+
header = "📁 beniya - ...#{current_path[-base_length..-1]}#{filter_text}"
|
109
|
+
else
|
110
|
+
header = "📁 beniya - ...#{current_path[-(@screen_width - 15)..-1]}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
puts "\e[7m#{header.ljust(@screen_width)}\e[0m" # reverse display
|
115
|
+
end
|
116
|
+
|
117
|
+
def draw_directory_list(entries, width, height)
|
118
|
+
start_index = [@keybind_handler.current_index - height / 2, 0].max
|
119
|
+
[start_index + height - 1, entries.length - 1].min
|
120
|
+
|
121
|
+
(0...height).each do |i|
|
122
|
+
entry_index = start_index + i
|
123
|
+
line_num = i + 2 # skip header
|
124
|
+
|
125
|
+
print "\e[#{line_num};1H" # set cursor position
|
126
|
+
|
127
|
+
if entry_index < entries.length
|
128
|
+
entry = entries[entry_index]
|
129
|
+
is_selected = entry_index == @keybind_handler.current_index
|
130
|
+
|
131
|
+
draw_entry_line(entry, width, is_selected)
|
132
|
+
else
|
133
|
+
# 左ペイン専用の安全な幅で空行を出力
|
134
|
+
safe_width = [width - 1, @screen_width / 2 - 1].min
|
135
|
+
print ' ' * safe_width
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def draw_entry_line(entry, width, is_selected)
|
141
|
+
# アイコンと色の設定
|
142
|
+
icon, color = get_entry_display_info(entry)
|
143
|
+
|
144
|
+
# 左ペイン専用の安全な幅を計算(右ペインにはみ出さないよう)
|
145
|
+
safe_width = [width - 1, @screen_width / 2 - 1].min
|
146
|
+
|
147
|
+
# ファイル名(必要に応じて切り詰め)
|
148
|
+
name = entry[:name]
|
149
|
+
max_name_length = safe_width - 10 # アイコンとサイズ情報分を除く
|
150
|
+
name = name[0...max_name_length - 3] + '...' if max_name_length > 0 && name.length > max_name_length
|
151
|
+
|
152
|
+
# サイズ情報
|
153
|
+
size_info = format_size(entry[:size])
|
154
|
+
|
155
|
+
# 行の内容を構築(安全な幅内で)
|
156
|
+
content_without_size = "#{icon} #{name}"
|
157
|
+
available_for_content = safe_width - size_info.length
|
158
|
+
|
159
|
+
line_content = if available_for_content > 0
|
160
|
+
content_without_size.ljust(available_for_content) + size_info
|
161
|
+
else
|
162
|
+
content_without_size
|
163
|
+
end
|
164
|
+
|
165
|
+
# 確実に safe_width を超えないよう切り詰め
|
166
|
+
line_content = line_content[0...safe_width]
|
167
|
+
|
168
|
+
if is_selected
|
169
|
+
selected_color = ColorHelper.color_to_selected_ansi(ConfigLoader.colors[:selected])
|
170
|
+
print "#{selected_color}#{line_content}#{ColorHelper.reset}"
|
171
|
+
else
|
172
|
+
print "#{color}#{line_content}#{ColorHelper.reset}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def get_entry_display_info(entry)
|
177
|
+
colors = ConfigLoader.colors
|
178
|
+
|
179
|
+
case entry[:type]
|
180
|
+
when 'directory'
|
181
|
+
color_code = ColorHelper.color_to_ansi(colors[:directory])
|
182
|
+
['📁', color_code]
|
183
|
+
when 'executable'
|
184
|
+
color_code = ColorHelper.color_to_ansi(colors[:executable])
|
185
|
+
['⚡', color_code]
|
186
|
+
else
|
187
|
+
case File.extname(entry[:name]).downcase
|
188
|
+
when '.rb'
|
189
|
+
['💎', "\e[31m"] # 赤
|
190
|
+
when '.js', '.ts'
|
191
|
+
['📜', "\e[33m"] # 黄
|
192
|
+
when '.txt', '.md'
|
193
|
+
color_code = ColorHelper.color_to_ansi(colors[:file])
|
194
|
+
['📄', color_code]
|
195
|
+
else
|
196
|
+
color_code = ColorHelper.color_to_ansi(colors[:file])
|
197
|
+
['📄', color_code]
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def format_size(size)
|
203
|
+
return ' ' if size == 0
|
204
|
+
|
205
|
+
if size < 1024
|
206
|
+
"#{size}B".rjust(6)
|
207
|
+
elsif size < 1024 * 1024
|
208
|
+
"#{(size / 1024.0).round(1)}K".rjust(6)
|
209
|
+
elsif size < 1024 * 1024 * 1024
|
210
|
+
"#{(size / (1024.0 * 1024)).round(1)}M".rjust(6)
|
211
|
+
else
|
212
|
+
"#{(size / (1024.0 * 1024 * 1024)).round(1)}G".rjust(6)
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
def draw_file_preview(selected_entry, width, height, left_offset)
|
217
|
+
(0...height).each do |i|
|
218
|
+
line_num = i + 2
|
219
|
+
# カーソル位置を左パネルの右端に設定
|
220
|
+
cursor_position = left_offset + 1
|
221
|
+
|
222
|
+
# 画面の境界を厳密に計算
|
223
|
+
max_chars_from_cursor = @screen_width - cursor_position
|
224
|
+
# 区切り線(│)分を除いて、さらに安全マージンを取る
|
225
|
+
safe_width = [max_chars_from_cursor - 2, width - 2, 0].max
|
226
|
+
|
227
|
+
print "\e[#{line_num};#{cursor_position}H" # カーソル位置設定
|
228
|
+
print '│' # 区切り線
|
229
|
+
|
230
|
+
content_to_print = ''
|
231
|
+
|
232
|
+
if selected_entry && i == 0
|
233
|
+
# プレビューヘッダー
|
234
|
+
header = " #{selected_entry[:name]} "
|
235
|
+
content_to_print = header
|
236
|
+
elsif selected_entry && selected_entry[:type] == 'file' && i >= 2
|
237
|
+
# ファイルプレビュー(折り返し対応)
|
238
|
+
preview_content = get_preview_content(selected_entry)
|
239
|
+
wrapped_lines = wrap_preview_lines(preview_content, safe_width - 1) # スペース分を除く
|
240
|
+
display_line_index = i - 2
|
241
|
+
|
242
|
+
if display_line_index < wrapped_lines.length
|
243
|
+
line = wrapped_lines[display_line_index] || ''
|
244
|
+
# スペースを先頭に追加
|
245
|
+
content_to_print = " #{line}"
|
246
|
+
else
|
247
|
+
content_to_print = ' '
|
248
|
+
end
|
249
|
+
else
|
250
|
+
content_to_print = ' '
|
251
|
+
end
|
252
|
+
|
253
|
+
# 絶対にsafe_widthを超えないよう強制的に切り詰める
|
254
|
+
if safe_width <= 0
|
255
|
+
# 表示スペースがない場合は何も出力しない
|
256
|
+
next
|
257
|
+
elsif display_width(content_to_print) > safe_width
|
258
|
+
# 表示幅ベースで切り詰める
|
259
|
+
content_to_print = truncate_to_width(content_to_print, safe_width)
|
260
|
+
end
|
261
|
+
|
262
|
+
# 出力(パディングなし、はみ出し防止のため)
|
263
|
+
print content_to_print
|
264
|
+
|
265
|
+
# 残りのスペースを埋める(ただし安全な範囲内のみ)
|
266
|
+
remaining_space = safe_width - display_width(content_to_print)
|
267
|
+
print ' ' * remaining_space if remaining_space > 0
|
268
|
+
end
|
269
|
+
end
|
270
|
+
|
271
|
+
def get_preview_content(entry)
|
272
|
+
return [] unless entry && entry[:type] == 'file'
|
273
|
+
|
274
|
+
preview = @file_preview.preview_file(entry[:path])
|
275
|
+
case preview[:type]
|
276
|
+
when 'text', 'code'
|
277
|
+
preview[:lines]
|
278
|
+
when 'binary'
|
279
|
+
["(#{ConfigLoader.message('file.binary_file')})", ConfigLoader.message('file.cannot_preview')]
|
280
|
+
when 'error'
|
281
|
+
["#{ConfigLoader.message('file.error_prefix')}:", preview[:message]]
|
282
|
+
else
|
283
|
+
["(#{ConfigLoader.message('file.cannot_preview')})"]
|
284
|
+
end
|
285
|
+
rescue StandardError
|
286
|
+
["(#{ConfigLoader.message('file.preview_error')})"]
|
287
|
+
end
|
288
|
+
|
289
|
+
def wrap_preview_lines(lines, max_width)
|
290
|
+
return [] if lines.empty? || max_width <= 0
|
291
|
+
|
292
|
+
wrapped_lines = []
|
293
|
+
|
294
|
+
lines.each do |line|
|
295
|
+
if display_width(line) <= max_width
|
296
|
+
# 短い行はそのまま追加
|
297
|
+
wrapped_lines << line
|
298
|
+
else
|
299
|
+
# 長い行は折り返し
|
300
|
+
remaining_line = line
|
301
|
+
while display_width(remaining_line) > max_width
|
302
|
+
# 単語境界で折り返すことを試みる
|
303
|
+
break_point = find_break_point(remaining_line, max_width)
|
304
|
+
wrapped_lines << remaining_line[0...break_point]
|
305
|
+
remaining_line = remaining_line[break_point..-1]
|
306
|
+
end
|
307
|
+
# 残りの部分を追加
|
308
|
+
wrapped_lines << remaining_line if remaining_line.length > 0
|
309
|
+
end
|
310
|
+
end
|
311
|
+
|
312
|
+
wrapped_lines
|
313
|
+
end
|
314
|
+
|
315
|
+
def display_width(string)
|
316
|
+
# 文字列の表示幅を計算する
|
317
|
+
# 日本語文字(全角)は幅2、ASCII文字(半角)は幅1として計算
|
318
|
+
width = 0
|
319
|
+
string.each_char do |char|
|
320
|
+
# 全角文字の判定
|
321
|
+
width += if char.ord > 127 || char.match?(/[あ-ん ア-ン 一-龯]/)
|
322
|
+
2
|
323
|
+
else
|
324
|
+
1
|
325
|
+
end
|
326
|
+
end
|
327
|
+
width
|
328
|
+
end
|
329
|
+
|
330
|
+
def truncate_to_width(string, max_width)
|
331
|
+
# 表示幅を指定して文字列を切り詰める
|
332
|
+
return string if display_width(string) <= max_width
|
333
|
+
|
334
|
+
current_width = 0
|
335
|
+
result = ''
|
336
|
+
|
337
|
+
string.each_char do |char|
|
338
|
+
char_width = char.ord > 127 || char.match?(/[あ-ん ア-ン 一-龯]/) ? 2 : 1
|
339
|
+
|
340
|
+
if current_width + char_width > max_width
|
341
|
+
# "..."を追加できるかチェック
|
342
|
+
result += '...' if max_width >= 3 && current_width <= max_width - 3
|
343
|
+
break
|
344
|
+
end
|
345
|
+
|
346
|
+
result += char
|
347
|
+
current_width += char_width
|
348
|
+
end
|
349
|
+
|
350
|
+
result
|
351
|
+
end
|
352
|
+
|
353
|
+
def find_break_point(line, max_width)
|
354
|
+
# 最大幅以内で適切な折り返し位置を見つける
|
355
|
+
return line.length if display_width(line) <= max_width
|
356
|
+
|
357
|
+
# 文字ごとに幅を計算しながら適切な位置を探す
|
358
|
+
current_width = 0
|
359
|
+
best_break_point = 0
|
360
|
+
space_break_point = nil
|
361
|
+
punct_break_point = nil
|
362
|
+
|
363
|
+
line.each_char.with_index do |char, index|
|
364
|
+
char_width = char.ord > 127 || char.match?(/[あ-ん ア-ン 一-龯]/) ? 2 : 1
|
365
|
+
|
366
|
+
break if current_width + char_width > max_width
|
367
|
+
|
368
|
+
current_width += char_width
|
369
|
+
best_break_point = index + 1
|
370
|
+
|
371
|
+
# スペースで区切れる位置を記録
|
372
|
+
space_break_point = index + 1 if char == ' ' && current_width > max_width * 0.5
|
373
|
+
|
374
|
+
# 日本語の句読点で区切れる位置を記録
|
375
|
+
punct_break_point = index + 1 if char.match?(/[、。,.!?]/) && current_width > max_width * 0.5
|
376
|
+
end
|
377
|
+
|
378
|
+
# 最適な折り返し位置を選択
|
379
|
+
space_break_point || punct_break_point || best_break_point
|
380
|
+
end
|
381
|
+
|
382
|
+
def get_display_entries
|
383
|
+
if @keybind_handler.filter_active?
|
384
|
+
# Get filtered entries from keybind_handler
|
385
|
+
all_entries = @directory_listing.list_entries
|
386
|
+
query = @keybind_handler.filter_query.downcase
|
387
|
+
query.empty? ? all_entries : all_entries.select { |entry| entry[:name].downcase.include?(query) }
|
388
|
+
else
|
389
|
+
@directory_listing.list_entries
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
def draw_footer
|
394
|
+
# 最下行から1行上に表示してスクロールを避ける
|
395
|
+
footer_line = @screen_height - 1
|
396
|
+
print "\e[#{footer_line};1H"
|
397
|
+
|
398
|
+
if @keybind_handler.filter_active?
|
399
|
+
if @keybind_handler.instance_variable_get(:@filter_mode)
|
400
|
+
help_text = "Filter mode: Type to filter, ESC to clear, Enter to apply, Backspace to delete"
|
401
|
+
else
|
402
|
+
help_text = "Filtered view active - Space to edit filter, ESC to clear filter"
|
403
|
+
end
|
404
|
+
else
|
405
|
+
help_text = ConfigLoader.message('help.full')
|
406
|
+
help_text = ConfigLoader.message('help.short') if help_text.length > @screen_width
|
407
|
+
end
|
408
|
+
|
409
|
+
# 文字列を確実に画面幅に合わせる
|
410
|
+
footer_content = help_text.ljust(@screen_width)[0...@screen_width]
|
411
|
+
print "\e[7m#{footer_content}\e[0m"
|
412
|
+
end
|
413
|
+
|
414
|
+
def handle_input
|
415
|
+
begin
|
416
|
+
input = STDIN.getch
|
417
|
+
rescue Errno::ENOTTY, Errno::ENODEV
|
418
|
+
# ターミナルでない環境(IDE等)では標準入力を使用
|
419
|
+
print "\n操作: "
|
420
|
+
input = STDIN.gets
|
421
|
+
return 'q' if input.nil?
|
422
|
+
input = input.chomp.downcase
|
423
|
+
return input[0] if input.length > 0
|
424
|
+
|
425
|
+
return 'q'
|
426
|
+
end
|
427
|
+
|
428
|
+
# 特殊キーの処理
|
429
|
+
if input == "\e"
|
430
|
+
# エスケープシーケンスの処理
|
431
|
+
next_char = begin
|
432
|
+
STDIN.read_nonblock(1)
|
433
|
+
rescue StandardError
|
434
|
+
nil
|
435
|
+
end
|
436
|
+
if next_char == '['
|
437
|
+
arrow_key = begin
|
438
|
+
STDIN.read_nonblock(1)
|
439
|
+
rescue StandardError
|
440
|
+
nil
|
441
|
+
end
|
442
|
+
input = case arrow_key
|
443
|
+
when 'A' # 上矢印
|
444
|
+
'k'
|
445
|
+
when 'B' # 下矢印
|
446
|
+
'j'
|
447
|
+
when 'C' # 右矢印
|
448
|
+
'l'
|
449
|
+
when 'D' # 左矢印
|
450
|
+
'h'
|
451
|
+
else
|
452
|
+
"\e" # ESCキー(そのまま保持)
|
453
|
+
end
|
454
|
+
else
|
455
|
+
input = "\e" # ESCキー(そのまま保持)
|
456
|
+
end
|
457
|
+
end
|
458
|
+
|
459
|
+
# キーバインドハンドラーに処理を委譲
|
460
|
+
result = @keybind_handler.handle_key(input)
|
461
|
+
|
462
|
+
# 終了処理(qキーのみ)
|
463
|
+
if input == 'q'
|
464
|
+
@running = false
|
465
|
+
end
|
466
|
+
end
|
467
|
+
end
|
468
|
+
end
|
469
|
+
|
data/lib/beniya.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "beniya/version"
|
4
|
+
require_relative "beniya/config"
|
5
|
+
require_relative "beniya/config_loader"
|
6
|
+
require_relative "beniya/color_helper"
|
7
|
+
require_relative "beniya/directory_listing"
|
8
|
+
require_relative "beniya/keybind_handler"
|
9
|
+
require_relative "beniya/file_preview"
|
10
|
+
require_relative "beniya/terminal_ui"
|
11
|
+
require_relative "beniya/application"
|
12
|
+
require_relative "beniya/file_opener"
|
13
|
+
require_relative "beniya/health_checker"
|
14
|
+
|
15
|
+
module Beniya
|
16
|
+
class Error < StandardError; end
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,161 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: beniya
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- beniya
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-08-17 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: io-console
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '0.6'
|
19
|
+
type: :runtime
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '0.6'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: pastel
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0.8'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0.8'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: tty-cursor
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0.7'
|
47
|
+
type: :runtime
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0.7'
|
54
|
+
- !ruby/object:Gem::Dependency
|
55
|
+
name: tty-screen
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0.8'
|
61
|
+
type: :runtime
|
62
|
+
prerelease: false
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - "~>"
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0.8'
|
68
|
+
- !ruby/object:Gem::Dependency
|
69
|
+
name: minitest
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - "~>"
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '5.0'
|
75
|
+
type: :development
|
76
|
+
prerelease: false
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '5.0'
|
82
|
+
- !ruby/object:Gem::Dependency
|
83
|
+
name: rake
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '13.0'
|
89
|
+
type: :development
|
90
|
+
prerelease: false
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '13.0'
|
96
|
+
- !ruby/object:Gem::Dependency
|
97
|
+
name: rubocop
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '1.21'
|
103
|
+
type: :development
|
104
|
+
prerelease: false
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - "~>"
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '1.21'
|
110
|
+
description: A terminal-based file manager inspired by Yazi, written in Ruby with
|
111
|
+
plugin support
|
112
|
+
email:
|
113
|
+
- masisz.1567@gmail.com
|
114
|
+
executables:
|
115
|
+
- beniya
|
116
|
+
extensions: []
|
117
|
+
extra_rdoc_files: []
|
118
|
+
files:
|
119
|
+
- README.md
|
120
|
+
- README_EN.md
|
121
|
+
- beniya.gemspec
|
122
|
+
- bin/beniya
|
123
|
+
- config_example.rb
|
124
|
+
- lib/beniya.rb
|
125
|
+
- lib/beniya/application.rb
|
126
|
+
- lib/beniya/color_helper.rb
|
127
|
+
- lib/beniya/config.rb
|
128
|
+
- lib/beniya/config_loader.rb
|
129
|
+
- lib/beniya/directory_listing.rb
|
130
|
+
- lib/beniya/file_opener.rb
|
131
|
+
- lib/beniya/file_preview.rb
|
132
|
+
- lib/beniya/health_checker.rb
|
133
|
+
- lib/beniya/keybind_handler.rb
|
134
|
+
- lib/beniya/terminal_ui.rb
|
135
|
+
- lib/beniya/version.rb
|
136
|
+
homepage: https://github.com/masisz/beniya
|
137
|
+
licenses:
|
138
|
+
- MIT
|
139
|
+
metadata:
|
140
|
+
allowed_push_host: https://rubygems.org
|
141
|
+
homepage_uri: https://github.com/masisz/beniya
|
142
|
+
source_code_uri: https://github.com/masisz/beniya
|
143
|
+
changelog_uri: https://github.com/masisz/beniya/blob/main/CHANGELOG.md
|
144
|
+
rdoc_options: []
|
145
|
+
require_paths:
|
146
|
+
- lib
|
147
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: 2.7.0
|
152
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
requirements: []
|
158
|
+
rubygems_version: 3.6.6
|
159
|
+
specification_version: 4
|
160
|
+
summary: Ruby file manager
|
161
|
+
test_files: []
|