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.
data/config_example.rb ADDED
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ # beniya configuration example
4
+ # Copy this file to ~/.config/beniya/config.rb to customize your settings
5
+
6
+ # Cross-platform file opener method
7
+ def get_system_open_command
8
+ case RbConfig::CONFIG['host_os']
9
+ when /mswin|mingw|cygwin/
10
+ 'start' # Windows
11
+ when /darwin/
12
+ 'open' # macOS
13
+ when /linux|bsd/
14
+ 'xdg-open' # Linux/BSD
15
+ else
16
+ 'open' # Fallback to open
17
+ end
18
+ end
19
+
20
+ # Get the appropriate open command for current platform
21
+ SYSTEM_OPEN = get_system_open_command
22
+
23
+ # Language setting
24
+ # Available languages: 'en' (English), 'ja' (Japanese)
25
+ # If not specified, language will be auto-detected from environment variables
26
+ LANGUAGE = 'en' # or 'ja'
27
+
28
+ # Application associations
29
+ # Define which applications to use for opening different file types
30
+ APPLICATIONS = {
31
+ # Text files - open with 'code' (VS Code)
32
+ %w[txt md rb py js ts html css json xml yaml yml] => 'code',
33
+
34
+ # Image files - open with default system app
35
+ %w[jpg jpeg png gif bmp svg webp] => SYSTEM_OPEN,
36
+
37
+ # Video files - open with default system app
38
+ %w[mp4 avi mkv mov wmv] => SYSTEM_OPEN,
39
+
40
+ # Documents - open with default system app
41
+ %w[pdf doc docx xls xlsx ppt pptx] => SYSTEM_OPEN,
42
+
43
+ # Default application for unspecified file types
44
+ :default => SYSTEM_OPEN
45
+ }
46
+
47
+ # Color scheme
48
+ # Define colors for different types of files and UI elements
49
+ # You can use various color formats:
50
+ # - Symbols: :blue, :red, :green, :yellow, :cyan, :magenta, :white, :black
51
+ # - HSL: {hsl: [hue(0-360), saturation(0-100), lightness(0-100)]}
52
+ # - RGB: {rgb: [red(0-255), green(0-255), blue(0-255)]}
53
+ # - HEX: {hex: "#ff0000"}
54
+ # - ANSI codes: "34" or 34
55
+ COLORS = {
56
+ # HSL color examples (Hue: 0-360, Saturation: 0-100%, Lightness: 0-100%)
57
+ directory: { hsl: [220, 80, 60] }, # Blue directory entries
58
+ file: { hsl: [0, 0, 90] }, # Light gray regular files
59
+ executable: { hsl: [120, 70, 50] }, # Green executable files
60
+ selected: { hsl: [50, 90, 70] }, # Yellow currently selected item
61
+ preview: { hsl: [180, 60, 65] }, # Cyan preview panel
62
+
63
+ # You can also mix different formats:
64
+ # directory: :blue, # Traditional symbol
65
+ # file: {rgb: [200, 200, 200]}, # RGB format
66
+ # executable: {hex: "#00ff00"}, # HEX format
67
+ # selected: "93", # ANSI code (bright yellow)
68
+ }
69
+
70
+ # Key bindings
71
+ # Customize keyboard shortcuts (not yet fully implemented)
72
+ KEYBINDS = {
73
+ quit: %w[q ESC],
74
+ up: %w[k UP],
75
+ down: %w[j DOWN],
76
+ left: %w[h LEFT],
77
+ right: %w[l RIGHT ENTER],
78
+ top: %w[g],
79
+ bottom: %w[G],
80
+ refresh: %w[r],
81
+ search: %w[/],
82
+ open_file: %w[o SPACE]
83
+ }
84
+
85
+ # You can also set language via environment variable:
86
+ # export BENIYA_LANG=ja # Set to 'ja' for Japanese, 'en' for English
87
+ # Note: Only BENIYA_LANG is used for language detection
88
+ # System LANG variable is ignored to ensure English is the default
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Beniya
4
+ class Application
5
+ def initialize(start_directory = Dir.pwd)
6
+ @start_directory = File.expand_path(start_directory)
7
+ # Load configuration including language settings
8
+ ConfigLoader.load_config
9
+ end
10
+
11
+ def run
12
+ # 各コンポーネントを初期化
13
+ directory_listing = DirectoryListing.new(@start_directory)
14
+ keybind_handler = KeybindHandler.new
15
+ file_preview = FilePreview.new
16
+ terminal_ui = TerminalUI.new
17
+
18
+ # アプリケーション開始
19
+ terminal_ui.start(directory_listing, keybind_handler, file_preview)
20
+ rescue Interrupt
21
+ puts "\n\n#{ConfigLoader.message('app.interrupted')}"
22
+ rescue StandardError => e
23
+ puts "\n#{ConfigLoader.message('app.error_occurred')}: #{e.message}"
24
+ puts e.backtrace.first(5).join("\n")
25
+ end
26
+ end
27
+ end
28
+
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Beniya
4
+ class ColorHelper
5
+ # HSLからRGBへの変換
6
+ def self.hsl_to_rgb(hue, saturation, lightness)
7
+ h = hue.to_f / 360.0
8
+ s = saturation.to_f / 100.0
9
+ l = lightness.to_f / 100.0
10
+
11
+ if s == 0
12
+ # 彩度が0の場合(グレースケール)
13
+ r = g = b = l
14
+ else
15
+ hue2rgb = lambda do |p, q, t|
16
+ t += 1 if t < 0
17
+ t -= 1 if t > 1
18
+ return p + (q - p) * 6 * t if t < 1.0/6
19
+ return q if t < 1.0/2
20
+ return p + (q - p) * (2.0/3 - t) * 6 if t < 2.0/3
21
+ p
22
+ end
23
+
24
+ q = l < 0.5 ? l * (1 + s) : l + s - l * s
25
+ p = 2 * l - q
26
+
27
+ r = hue2rgb.call(p, q, h + 1.0/3)
28
+ g = hue2rgb.call(p, q, h)
29
+ b = hue2rgb.call(p, q, h - 1.0/3)
30
+ end
31
+
32
+ [(r * 255).round, (g * 255).round, (b * 255).round]
33
+ end
34
+
35
+ # 色設定をANSIエスケープコードに変換
36
+ def self.color_to_ansi(color_config)
37
+ case color_config
38
+ when Hash
39
+ if color_config[:hsl]
40
+ # HSL形式: {hsl: [240, 100, 50]}
41
+ hue, saturation, lightness = color_config[:hsl]
42
+ r, g, b = hsl_to_rgb(hue, saturation, lightness)
43
+ "\e[38;2;#{r};#{g};#{b}m"
44
+ elsif color_config[:rgb]
45
+ # RGB形式: {rgb: [100, 150, 200]}
46
+ r, g, b = color_config[:rgb]
47
+ "\e[38;2;#{r};#{g};#{b}m"
48
+ elsif color_config[:hex]
49
+ # HEX形式: {hex: "#ff0000"}
50
+ hex = color_config[:hex].gsub('#', '')
51
+ r = hex[0..1].to_i(16)
52
+ g = hex[2..3].to_i(16)
53
+ b = hex[4..5].to_i(16)
54
+ "\e[38;2;#{r};#{g};#{b}m"
55
+ else
56
+ # デフォルト(白)
57
+ "\e[37m"
58
+ end
59
+ when Symbol
60
+ # 従来のシンボル形式をANSIコードに変換
61
+ symbol_to_ansi(color_config)
62
+ when String
63
+ # 直接ANSIコードまたは名前が指定された場合
64
+ if color_config.match?(/^\d+$/)
65
+ "\e[#{color_config}m"
66
+ else
67
+ name_to_ansi(color_config)
68
+ end
69
+ when Integer
70
+ # 数値が直接指定された場合
71
+ "\e[#{color_config}m"
72
+ else
73
+ # デフォルト(白)
74
+ "\e[37m"
75
+ end
76
+ end
77
+
78
+ # シンボルをANSIコードに変換
79
+ def self.symbol_to_ansi(symbol)
80
+ case symbol
81
+ when :black then "\e[30m"
82
+ when :red then "\e[31m"
83
+ when :green then "\e[32m"
84
+ when :yellow then "\e[33m"
85
+ when :blue then "\e[34m"
86
+ when :magenta then "\e[35m"
87
+ when :cyan then "\e[36m"
88
+ when :white then "\e[37m"
89
+ when :bright_black then "\e[90m"
90
+ when :bright_red then "\e[91m"
91
+ when :bright_green then "\e[92m"
92
+ when :bright_yellow then "\e[93m"
93
+ when :bright_blue then "\e[94m"
94
+ when :bright_magenta then "\e[95m"
95
+ when :bright_cyan then "\e[96m"
96
+ when :bright_white then "\e[97m"
97
+ else "\e[37m" # デフォルト(白)
98
+ end
99
+ end
100
+
101
+ # 色名をANSIコードに変換
102
+ def self.name_to_ansi(name)
103
+ symbol_to_ansi(name.to_sym)
104
+ end
105
+
106
+ # 背景色用のANSIコードを生成
107
+ def self.color_to_bg_ansi(color_config)
108
+ ansi_code = color_to_ansi(color_config)
109
+ # 前景色(38)を背景色(48)に変換
110
+ ansi_code.gsub('38;', '48;')
111
+ end
112
+
113
+ # リセットコード
114
+ def self.reset
115
+ "\e[0m"
116
+ end
117
+
118
+ # 選択状態(反転表示)用のANSIコードを生成
119
+ def self.color_to_selected_ansi(color_config)
120
+ color_code = color_to_ansi(color_config)
121
+ # 反転表示を追加
122
+ color_code.gsub("\e[", "\e[7;").gsub("m", ";7m")
123
+ end
124
+
125
+ # プリセットHSLカラー
126
+ def self.preset_hsl_colors
127
+ {
128
+ # ディレクトリ用の青系
129
+ directory_blue: { hsl: [220, 80, 60] },
130
+ directory_cyan: { hsl: [180, 70, 55] },
131
+
132
+ # 実行ファイル用の緑系
133
+ executable_green: { hsl: [120, 70, 50] },
134
+ executable_lime: { hsl: [90, 80, 55] },
135
+
136
+ # テキストファイル用
137
+ text_white: { hsl: [0, 0, 90] },
138
+ text_gray: { hsl: [0, 0, 70] },
139
+
140
+ # 選択状態用
141
+ selected_yellow: { hsl: [50, 90, 70] },
142
+ selected_orange: { hsl: [30, 85, 65] },
143
+
144
+ # プレビュー用
145
+ preview_cyan: { hsl: [180, 60, 65] },
146
+ preview_purple: { hsl: [270, 50, 70] }
147
+ }
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Beniya
4
+ class Config
5
+ # Default language settings
6
+ DEFAULT_LANGUAGE = 'en'
7
+ AVAILABLE_LANGUAGES = %w[en ja].freeze
8
+
9
+ # Multi-language message definitions
10
+ MESSAGES = {
11
+ 'en' => {
12
+ # Application messages
13
+ 'app.interrupted' => 'beniya interrupted',
14
+ 'app.error_occurred' => 'Error occurred',
15
+ 'app.terminated' => 'beniya terminated',
16
+
17
+ # File operations
18
+ 'file.not_found' => 'File not found',
19
+ 'file.not_readable' => 'File not readable',
20
+ 'file.read_error' => 'File read error',
21
+ 'file.binary_file' => 'Binary file',
22
+ 'file.cannot_preview' => 'Cannot preview',
23
+ 'file.encoding_error' => 'Character encoding error - cannot read file',
24
+ 'file.preview_error' => 'Preview error',
25
+ 'file.error_prefix' => 'Error',
26
+
27
+ # Keybind messages
28
+ 'keybind.invalid_key' => 'invalid key',
29
+ 'keybind.search_text' => 'Search text: ',
30
+ 'keybind.no_matches' => 'No matches found.',
31
+ 'keybind.press_any_key' => 'Press any key to continue...',
32
+
33
+ # UI messages
34
+ 'ui.operation_prompt' => 'Operation: ',
35
+
36
+ # Help text
37
+ 'help.full' => 'j/k:move h:back l:enter o/Space:open g/G:top/bottom r:refresh q:quit',
38
+ 'help.short' => 'j/k:move h:back l:enter o:open q:quit',
39
+
40
+ # Health check messages
41
+ 'health.title' => 'beniya Health Check',
42
+ 'health.ruby_version' => 'Ruby version',
43
+ 'health.required_gems' => 'Required gems',
44
+ 'health.fzf' => 'fzf (file search)',
45
+ 'health.rga' => 'rga (content search)',
46
+ 'health.file_opener' => 'System file opener',
47
+ 'health.summary' => 'Summary:',
48
+ 'health.ok' => 'OK',
49
+ 'health.warnings' => 'Warnings',
50
+ 'health.errors' => 'Errors',
51
+ 'health.all_passed' => 'All checks passed! beniya is ready to use.',
52
+ 'health.critical_missing' => 'Some critical components are missing. beniya may not work properly.',
53
+ 'health.optional_missing' => 'Some optional features are unavailable. Basic functionality will work.',
54
+ 'health.all_gems_installed' => 'All required gems installed',
55
+ 'health.missing_gems' => 'Missing gems',
56
+ 'health.gem_install_instruction' => 'Run: gem install',
57
+ 'health.tool_not_found' => 'not found',
58
+ 'health.unknown_platform' => 'Unknown platform',
59
+ 'health.file_open_may_not_work' => 'File opening may not work properly',
60
+ 'health.macos_opener' => 'macOS file opener',
61
+ 'health.linux_opener' => 'Linux file opener',
62
+ 'health.windows_opener' => 'Windows file opener',
63
+ 'health.install_brew' => 'Install: brew install',
64
+ 'health.install_apt' => 'Install: apt install',
65
+ 'health.install_guide' => 'Check installation guide for your platform',
66
+ 'health.rga_releases' => 'Install: https://github.com/phiresky/ripgrep-all/releases',
67
+ 'health.ruby_upgrade_needed' => 'Please upgrade Ruby to version 2.7.0 or higher'
68
+ },
69
+
70
+ 'ja' => {
71
+ # Application messages
72
+ 'app.interrupted' => 'beniyaを中断しました',
73
+ 'app.error_occurred' => 'エラーが発生しました',
74
+ 'app.terminated' => 'beniyaを終了しました',
75
+
76
+ # File operations
77
+ 'file.not_found' => 'ファイルが見つかりません',
78
+ 'file.not_readable' => 'ファイルを読み取れません',
79
+ 'file.read_error' => 'ファイル読み込みエラー',
80
+ 'file.binary_file' => 'バイナリファイル',
81
+ 'file.cannot_preview' => 'プレビューできません',
82
+ 'file.encoding_error' => '文字エンコーディングエラー - ファイルを読み取れません',
83
+ 'file.preview_error' => 'プレビューエラー',
84
+ 'file.error_prefix' => 'エラー',
85
+
86
+ # Keybind messages
87
+ 'keybind.invalid_key' => '無効なキー',
88
+ 'keybind.search_text' => '検索テキスト: ',
89
+ 'keybind.no_matches' => 'マッチするものが見つかりません。',
90
+ 'keybind.press_any_key' => '何かキーを押して続行...',
91
+
92
+ # UI messages
93
+ 'ui.operation_prompt' => '操作: ',
94
+
95
+ # Help text
96
+ 'help.full' => 'j/k:移動 h:戻る l:入る o/Space:開く g/G:先頭/末尾 r:更新 q:終了',
97
+ 'help.short' => 'j/k:移動 h:戻る l:入る o:開く q:終了',
98
+
99
+ # Health check messages
100
+ 'health.title' => 'beniya ヘルスチェック',
101
+ 'health.ruby_version' => 'Ruby バージョン',
102
+ 'health.required_gems' => '必須 gem',
103
+ 'health.fzf' => 'fzf (ファイル検索)',
104
+ 'health.rga' => 'rga (内容検索)',
105
+ 'health.file_opener' => 'システムファイルオープナー',
106
+ 'health.summary' => 'サマリー:',
107
+ 'health.ok' => 'OK',
108
+ 'health.warnings' => '警告',
109
+ 'health.errors' => 'エラー',
110
+ 'health.all_passed' => '全てのチェックが完了しました!beniyaは使用可能です。',
111
+ 'health.critical_missing' => '重要なコンポーネントが不足しています。beniyaは正常に動作しない可能性があります。',
112
+ 'health.optional_missing' => 'オプション機能が利用できません。基本機能は動作します。',
113
+ 'health.all_gems_installed' => '全ての必須gemがインストールされています',
114
+ 'health.missing_gems' => '不足しているgem',
115
+ 'health.gem_install_instruction' => '実行: gem install',
116
+ 'health.tool_not_found' => 'が見つかりません',
117
+ 'health.unknown_platform' => '不明なプラットフォーム',
118
+ 'health.file_open_may_not_work' => 'ファイルオープンが正常に動作しない可能性があります',
119
+ 'health.macos_opener' => 'macOS ファイルオープナー',
120
+ 'health.linux_opener' => 'Linux ファイルオープナー',
121
+ 'health.windows_opener' => 'Windows ファイルオープナー',
122
+ 'health.install_brew' => 'インストール: brew install',
123
+ 'health.install_apt' => 'インストール: apt install',
124
+ 'health.install_guide' => 'お使いのプラットフォーム向けのインストールガイドを確認してください',
125
+ 'health.rga_releases' => 'インストール: https://github.com/phiresky/ripgrep-all/releases',
126
+ 'health.ruby_upgrade_needed' => 'Rubyをバージョン2.7.0以上にアップグレードしてください'
127
+ }
128
+ }.freeze
129
+
130
+ class << self
131
+ def current_language
132
+ @current_language ||= detect_language
133
+ end
134
+
135
+ def current_language=(lang)
136
+ if AVAILABLE_LANGUAGES.include?(lang.to_s)
137
+ @current_language = lang.to_s
138
+ else
139
+ raise ArgumentError, "Unsupported language: #{lang}. Available: #{AVAILABLE_LANGUAGES.join(', ')}"
140
+ end
141
+ end
142
+
143
+ def message(key, **interpolations)
144
+ msg = MESSAGES.dig(current_language, key) || MESSAGES.dig(DEFAULT_LANGUAGE, key) || key
145
+
146
+ # Simple interpolation support
147
+ interpolations.each do |placeholder, value|
148
+ msg = msg.gsub("%{#{placeholder}}", value.to_s)
149
+ end
150
+
151
+ msg
152
+ end
153
+
154
+ def available_languages
155
+ AVAILABLE_LANGUAGES.dup
156
+ end
157
+
158
+ def reset_language!
159
+ @current_language = nil
160
+ end
161
+
162
+ private
163
+
164
+ def detect_language
165
+ # Only BENIYA_LANG environment variable takes precedence
166
+ # This ensures English is default unless explicitly requested
167
+ env_lang = ENV['BENIYA_LANG']
168
+
169
+ if env_lang && !env_lang.empty?
170
+ # Extract language code (e.g., 'ja_JP.UTF-8' -> 'ja')
171
+ lang_code = env_lang.split(/[_.]/).first&.downcase
172
+ return lang_code if AVAILABLE_LANGUAGES.include?(lang_code)
173
+ end
174
+
175
+ DEFAULT_LANGUAGE
176
+ end
177
+ end
178
+ end
179
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'config'
4
+
5
+ module Beniya
6
+ class ConfigLoader
7
+ CONFIG_PATH = File.expand_path('~/.config/beniya/config.rb').freeze
8
+
9
+ class << self
10
+ def load_config
11
+ @config ||= if File.exist?(CONFIG_PATH)
12
+ load_config_file
13
+ else
14
+ default_config
15
+ end
16
+ end
17
+
18
+ def reload_config!
19
+ @config = nil
20
+ load_config
21
+ end
22
+
23
+ def applications
24
+ load_config[:applications]
25
+ end
26
+
27
+ def colors
28
+ load_config[:colors]
29
+ end
30
+
31
+ def keybinds
32
+ load_config[:keybinds]
33
+ end
34
+
35
+ def language
36
+ load_config[:language] || Config.current_language
37
+ end
38
+
39
+ def set_language(lang)
40
+ Config.current_language = lang
41
+ # Update config if it's user-defined
42
+ if @config
43
+ @config[:language] = lang
44
+ end
45
+ end
46
+
47
+ def message(key, **interpolations)
48
+ Config.message(key, **interpolations)
49
+ end
50
+
51
+ private
52
+
53
+ def load_config_file
54
+ # 設定ファイルを実行してグローバル定数を定義
55
+ load CONFIG_PATH
56
+ config = {
57
+ applications: Object.const_get(:APPLICATIONS),
58
+ colors: Object.const_get(:COLORS),
59
+ keybinds: Object.const_get(:KEYBINDS)
60
+ }
61
+
62
+ # Load language setting if defined
63
+ if Object.const_defined?(:LANGUAGE)
64
+ language = Object.const_get(:LANGUAGE)
65
+ config[:language] = language
66
+ Config.current_language = language if Config.available_languages.include?(language.to_s)
67
+ end
68
+
69
+ config
70
+ rescue StandardError => e
71
+ warn "Failed to load config file: #{e.message}"
72
+ warn 'Using default configuration'
73
+ default_config
74
+ end
75
+
76
+ def default_config
77
+ {
78
+ applications: {
79
+ %w[txt md rb py js html css json xml yaml yml] => 'code',
80
+ %w[jpg jpeg png gif bmp svg webp] => 'open',
81
+ %w[mp4 avi mkv mov wmv] => 'open',
82
+ %w[pdf] => 'open',
83
+ %w[doc docx xls xlsx ppt pptx] => 'open',
84
+ :default => 'open'
85
+ },
86
+ colors: {
87
+ directory: { hsl: [220, 80, 60] }, # Blue
88
+ file: { hsl: [0, 0, 90] }, # Light gray
89
+ executable: { hsl: [120, 70, 50] }, # Green
90
+ selected: { hsl: [50, 90, 70] }, # Yellow
91
+ preview: { hsl: [180, 60, 65] } # Cyan
92
+ },
93
+ keybinds: {
94
+ quit: %w[q ESC],
95
+ up: %w[k UP],
96
+ down: %w[j DOWN],
97
+ left: %w[h LEFT],
98
+ right: %w[l RIGHT ENTER],
99
+ top: %w[g],
100
+ bottom: %w[G],
101
+ refresh: %w[r],
102
+ search: %w[/],
103
+ open_file: %w[o SPACE]
104
+ }
105
+ }
106
+ end
107
+ end
108
+ end
109
+ end
110
+
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+
5
+ module Beniya
6
+ class DirectoryListing
7
+ attr_reader :current_path
8
+
9
+ def initialize(path = Dir.pwd)
10
+ @current_path = File.expand_path(path)
11
+ @entries = []
12
+ refresh
13
+ end
14
+
15
+ def list_entries
16
+ @entries
17
+ end
18
+
19
+ def refresh
20
+ return unless File.directory?(@current_path)
21
+
22
+ @entries = []
23
+
24
+ Dir.entries(@current_path).each do |name|
25
+ next if name == '.'
26
+
27
+ full_path = File.join(@current_path, name)
28
+ entry = {
29
+ name: name,
30
+ path: full_path,
31
+ type: determine_file_type(full_path),
32
+ size: safe_file_size(full_path),
33
+ modified: safe_file_mtime(full_path)
34
+ }
35
+ @entries << entry
36
+ end
37
+
38
+ sort_entries!
39
+ end
40
+
41
+ def navigate_to(target)
42
+ return false if target.nil? || target.empty?
43
+
44
+ new_path = File.join(@current_path, target)
45
+
46
+ if File.directory?(new_path) && File.readable?(new_path)
47
+ @current_path = File.expand_path(new_path)
48
+ refresh
49
+ true
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ def navigate_to_parent
56
+ parent_path = File.dirname(@current_path)
57
+
58
+ # 同じパスの場合は移動しない(ルートディレクトリに到達)
59
+ return false if parent_path == @current_path
60
+
61
+ if File.directory?(parent_path) && File.readable?(parent_path)
62
+ @current_path = parent_path
63
+ refresh
64
+ true
65
+ else
66
+ false
67
+ end
68
+ end
69
+
70
+ private
71
+
72
+ def determine_file_type(path)
73
+ return 'directory' if File.directory?(path)
74
+ return 'executable' if File.executable?(path) && !File.directory?(path)
75
+
76
+ 'file'
77
+ end
78
+
79
+ def safe_file_size(path)
80
+ File.size(path)
81
+ rescue StandardError
82
+ 0
83
+ end
84
+
85
+ def safe_file_mtime(path)
86
+ File.mtime(path)
87
+ rescue StandardError
88
+ Time.now
89
+ end
90
+
91
+ def sort_entries!
92
+ @entries.sort_by! do |entry|
93
+ # ディレクトリを最初に、その後ファイル名でソート
94
+ [entry[:type] == 'directory' ? 0 : 1, entry[:name].downcase]
95
+ end
96
+ end
97
+ end
98
+ end
99
+