ukiryu 0.1.0 → 0.1.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 +4 -4
- data/.github/workflows/docs.yml +63 -0
- data/.github/workflows/links.yml +99 -0
- data/.github/workflows/rake.yml +19 -0
- data/.github/workflows/release.yml +27 -0
- data/.gitignore +18 -4
- data/.rubocop.yml +1 -0
- data/.rubocop_todo.yml +213 -0
- data/Gemfile +12 -8
- data/README.adoc +613 -0
- data/Rakefile +2 -2
- data/docs/assets/logo.svg +1 -0
- data/exe/ukiryu +11 -0
- data/lib/ukiryu/action/base.rb +77 -0
- data/lib/ukiryu/cache.rb +199 -0
- data/lib/ukiryu/cli.rb +133 -307
- data/lib/ukiryu/cli_commands/base_command.rb +155 -0
- data/lib/ukiryu/cli_commands/commands_command.rb +120 -0
- data/lib/ukiryu/cli_commands/commands_command.rb.fixed +40 -0
- data/lib/ukiryu/cli_commands/config_command.rb +249 -0
- data/lib/ukiryu/cli_commands/describe_command.rb +326 -0
- data/lib/ukiryu/cli_commands/describe_command.rb.fixed +254 -0
- data/lib/ukiryu/cli_commands/exec_inline_command.rb.fixed +180 -0
- data/lib/ukiryu/cli_commands/extract_command.rb +84 -0
- data/lib/ukiryu/cli_commands/info_command.rb +156 -0
- data/lib/ukiryu/cli_commands/list_command.rb +70 -0
- data/lib/ukiryu/cli_commands/opts_command.rb +106 -0
- data/lib/ukiryu/cli_commands/opts_command.rb.fixed +105 -0
- data/lib/ukiryu/cli_commands/response_formatter.rb +240 -0
- data/lib/ukiryu/cli_commands/run_command.rb +375 -0
- data/lib/ukiryu/cli_commands/run_file_command.rb +215 -0
- data/lib/ukiryu/cli_commands/system_command.rb +90 -0
- data/lib/ukiryu/cli_commands/validate_command.rb +87 -0
- data/lib/ukiryu/cli_commands/version_command.rb +16 -0
- data/lib/ukiryu/cli_commands/which_command.rb +166 -0
- data/lib/ukiryu/command_builder.rb +205 -0
- data/lib/ukiryu/config/env_provider.rb +64 -0
- data/lib/ukiryu/config/env_schema.rb +63 -0
- data/lib/ukiryu/config/override_resolver.rb +68 -0
- data/lib/ukiryu/config/type_converter.rb +59 -0
- data/lib/ukiryu/config.rb +249 -0
- data/lib/ukiryu/errors.rb +3 -0
- data/lib/ukiryu/executable_locator.rb +114 -0
- data/lib/ukiryu/execution/command_info.rb +64 -0
- data/lib/ukiryu/execution/metadata.rb +97 -0
- data/lib/ukiryu/execution/output.rb +144 -0
- data/lib/ukiryu/execution/result.rb +194 -0
- data/lib/ukiryu/execution.rb +15 -0
- data/lib/ukiryu/execution_context.rb +251 -0
- data/lib/ukiryu/executor.rb +76 -493
- data/lib/ukiryu/extractors/base_extractor.rb +63 -0
- data/lib/ukiryu/extractors/extractor.rb +150 -0
- data/lib/ukiryu/extractors/help_parser.rb +188 -0
- data/lib/ukiryu/extractors/native_extractor.rb +47 -0
- data/lib/ukiryu/io.rb +196 -0
- data/lib/ukiryu/logger.rb +544 -0
- data/lib/ukiryu/models/argument.rb +28 -0
- data/lib/ukiryu/models/argument_definition.rb +119 -0
- data/lib/ukiryu/models/arguments.rb +113 -0
- data/lib/ukiryu/models/command_definition.rb +176 -0
- data/lib/ukiryu/models/command_info.rb +37 -0
- data/lib/ukiryu/models/components.rb +107 -0
- data/lib/ukiryu/models/env_var_definition.rb +30 -0
- data/lib/ukiryu/models/error_response.rb +41 -0
- data/lib/ukiryu/models/execution_metadata.rb +31 -0
- data/lib/ukiryu/models/execution_report.rb +236 -0
- data/lib/ukiryu/models/exit_codes.rb +74 -0
- data/lib/ukiryu/models/flag_definition.rb +67 -0
- data/lib/ukiryu/models/option_definition.rb +102 -0
- data/lib/ukiryu/models/output_info.rb +25 -0
- data/lib/ukiryu/models/platform_profile.rb +153 -0
- data/lib/ukiryu/models/routing.rb +211 -0
- data/lib/ukiryu/models/search_paths.rb +39 -0
- data/lib/ukiryu/models/success_response.rb +85 -0
- data/lib/ukiryu/models/tool_definition.rb +145 -0
- data/lib/ukiryu/models/tool_metadata.rb +82 -0
- data/lib/ukiryu/models/validation_result.rb +80 -0
- data/lib/ukiryu/models/version_compatibility.rb +152 -0
- data/lib/ukiryu/models/version_detection.rb +39 -0
- data/lib/ukiryu/models.rb +23 -0
- data/lib/ukiryu/options/base.rb +95 -0
- data/lib/ukiryu/options_builder/formatter.rb +87 -0
- data/lib/ukiryu/options_builder/validator.rb +43 -0
- data/lib/ukiryu/options_builder.rb +311 -0
- data/lib/ukiryu/platform.rb +6 -6
- data/lib/ukiryu/registry.rb +143 -183
- data/lib/ukiryu/response/base.rb +217 -0
- data/lib/ukiryu/runtime.rb +179 -0
- data/lib/ukiryu/schema_validator.rb +8 -10
- data/lib/ukiryu/shell/bash.rb +3 -3
- data/lib/ukiryu/shell/cmd.rb +4 -4
- data/lib/ukiryu/shell/fish.rb +1 -1
- data/lib/ukiryu/shell/powershell.rb +3 -3
- data/lib/ukiryu/shell/sh.rb +1 -1
- data/lib/ukiryu/shell/zsh.rb +1 -1
- data/lib/ukiryu/shell.rb +146 -39
- data/lib/ukiryu/thor_ext.rb +208 -0
- data/lib/ukiryu/tool.rb +649 -258
- data/lib/ukiryu/tool_index.rb +224 -0
- data/lib/ukiryu/tools/base.rb +381 -0
- data/lib/ukiryu/tools/class_generator.rb +132 -0
- data/lib/ukiryu/tools/executable_finder.rb +29 -0
- data/lib/ukiryu/tools/generator.rb +154 -0
- data/lib/ukiryu/tools.rb +109 -0
- data/lib/ukiryu/type.rb +28 -43
- data/lib/ukiryu/validation/constraints.rb +281 -0
- data/lib/ukiryu/validation/validator.rb +188 -0
- data/lib/ukiryu/validation.rb +21 -0
- data/lib/ukiryu/version.rb +1 -1
- data/lib/ukiryu/version_detector.rb +51 -0
- data/lib/ukiryu.rb +31 -15
- data/ukiryu-proposal.md +2952 -0
- data/ukiryu.gemspec +18 -14
- metadata +137 -5
- data/.github/workflows/test.yml +0 -143
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'singleton'
|
|
4
|
+
|
|
5
|
+
require_relative 'platform'
|
|
6
|
+
require_relative 'shell'
|
|
7
|
+
require_relative 'config'
|
|
8
|
+
|
|
9
|
+
module Ukiryu
|
|
10
|
+
# Runtime singleton for centralized platform and shell detection.
|
|
11
|
+
#
|
|
12
|
+
# This class provides a single source of truth for platform and shell
|
|
13
|
+
# detection across the entire application, eliminating redundant detection
|
|
14
|
+
# calls and ensuring consistency.
|
|
15
|
+
#
|
|
16
|
+
# @example
|
|
17
|
+
# platform = Ukiryu::Runtime.instance.platform
|
|
18
|
+
# shell = Ukiryu::Runtime.instance.shell
|
|
19
|
+
class Runtime
|
|
20
|
+
include Singleton
|
|
21
|
+
|
|
22
|
+
# Initialize the runtime with cached values
|
|
23
|
+
def initialize
|
|
24
|
+
@platform = nil
|
|
25
|
+
@shell = nil
|
|
26
|
+
@platform_cached = false
|
|
27
|
+
@shell_cached = false
|
|
28
|
+
@locked = false
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Get the current platform (cached)
|
|
32
|
+
#
|
|
33
|
+
# @return [Symbol] the detected platform (:macos, :linux, :windows)
|
|
34
|
+
def platform
|
|
35
|
+
return @platform if @platform_cached
|
|
36
|
+
|
|
37
|
+
@platform = Platform.detect
|
|
38
|
+
@platform_cached = true
|
|
39
|
+
@platform
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Get the current shell (cached)
|
|
43
|
+
#
|
|
44
|
+
# Priority:
|
|
45
|
+
# 1. Explicitly set shell (via shell=)
|
|
46
|
+
# 2. Config.shell (from --shell CLI option, UKIRYU_SHELL env, or programmatic config)
|
|
47
|
+
# 3. Auto-detected shell
|
|
48
|
+
#
|
|
49
|
+
# @return [Symbol] the detected shell
|
|
50
|
+
def shell
|
|
51
|
+
return @shell if @shell_cached
|
|
52
|
+
|
|
53
|
+
# Check for explicit override
|
|
54
|
+
override = shell_override
|
|
55
|
+
if override
|
|
56
|
+
@shell = override
|
|
57
|
+
@shell_cached = true
|
|
58
|
+
return @shell
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Auto-detect
|
|
62
|
+
@shell = Shell.detect
|
|
63
|
+
@shell_cached = true
|
|
64
|
+
@shell
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Manually set the platform (for testing)
|
|
68
|
+
#
|
|
69
|
+
# @param value [Symbol] the platform to set
|
|
70
|
+
def platform=(value)
|
|
71
|
+
raise 'Runtime is locked' if @locked
|
|
72
|
+
|
|
73
|
+
@platform = value&.to_sym
|
|
74
|
+
@platform_cached = true
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Manually set the shell (for testing)
|
|
78
|
+
#
|
|
79
|
+
# @param value [Symbol] the shell to set
|
|
80
|
+
def shell=(value)
|
|
81
|
+
raise 'Runtime is locked' if @locked
|
|
82
|
+
|
|
83
|
+
@shell = value&.to_sym
|
|
84
|
+
@shell_cached = true
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Lock the runtime to prevent further changes
|
|
88
|
+
#
|
|
89
|
+
# This should be called after initial configuration is complete.
|
|
90
|
+
def lock!
|
|
91
|
+
@locked = true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Reset the runtime cache (for testing)
|
|
95
|
+
#
|
|
96
|
+
# @api private
|
|
97
|
+
def reset!
|
|
98
|
+
@platform = nil
|
|
99
|
+
@shell = nil
|
|
100
|
+
@platform_cached = false
|
|
101
|
+
@shell_cached = false
|
|
102
|
+
@locked = false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Get the platform class for the current platform
|
|
106
|
+
#
|
|
107
|
+
# @return [Class] the platform class
|
|
108
|
+
def platform_class
|
|
109
|
+
Platform.class_for(platform)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Get the shell class for the current shell
|
|
113
|
+
#
|
|
114
|
+
# @return [Class] the shell class
|
|
115
|
+
def shell_class
|
|
116
|
+
Shell.class_for(shell)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Check if running on a specific platform
|
|
120
|
+
#
|
|
121
|
+
# @param plat [Symbol] the platform to check
|
|
122
|
+
# @return [Boolean] true if running on the specified platform
|
|
123
|
+
def on_platform?(plat)
|
|
124
|
+
platform == plat.to_sym
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Check if using a specific shell
|
|
128
|
+
#
|
|
129
|
+
# @param sh [Symbol] the shell to check
|
|
130
|
+
# @return [Boolean] true if using the specified shell
|
|
131
|
+
def using_shell?(sh)
|
|
132
|
+
shell == sh.to_sym
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Check if running on Windows
|
|
136
|
+
#
|
|
137
|
+
# @return [Boolean] true if on Windows
|
|
138
|
+
def windows?
|
|
139
|
+
on_platform?(:windows)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Check if running on macOS
|
|
143
|
+
#
|
|
144
|
+
# @return [Boolean] true if on macOS
|
|
145
|
+
def macos?
|
|
146
|
+
on_platform?(:macos)
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# Check if running on Linux
|
|
150
|
+
#
|
|
151
|
+
# @return [Boolean] true if on Linux
|
|
152
|
+
def linux?
|
|
153
|
+
on_platform?(:linux)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Check if using a Unix-like shell
|
|
157
|
+
#
|
|
158
|
+
# @return [Boolean] true if using bash, zsh, fish, or sh
|
|
159
|
+
def unix_shell?
|
|
160
|
+
%i[bash zsh fish sh].include?(shell)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Check if using a Windows shell
|
|
164
|
+
#
|
|
165
|
+
# @return [Boolean] true if using powershell or cmd
|
|
166
|
+
def windows_shell?
|
|
167
|
+
%i[powershell cmd].include?(shell)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
# Get shell override from Config
|
|
173
|
+
#
|
|
174
|
+
# @return [Symbol, nil] the shell override or nil
|
|
175
|
+
def shell_override
|
|
176
|
+
Config.shell
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require 'json'
|
|
4
4
|
begin
|
|
5
|
-
require
|
|
5
|
+
require 'json-schema'
|
|
6
6
|
rescue LoadError
|
|
7
7
|
# json-schema is optional - only needed for schema validation
|
|
8
8
|
end
|
|
9
|
-
require
|
|
9
|
+
require 'yaml'
|
|
10
10
|
|
|
11
11
|
module Ukiryu
|
|
12
12
|
# Schema validator for YAML tool profiles
|
|
@@ -23,15 +23,13 @@ module Ukiryu
|
|
|
23
23
|
# @return [Array<String>] list of validation errors (empty if valid)
|
|
24
24
|
def validate_profile(profile, options = {})
|
|
25
25
|
# Check if json-schema gem is available
|
|
26
|
-
unless defined?(JSON::Validator)
|
|
27
|
-
return ["json-schema gem not installed. Add 'json-schema' to Gemfile for schema validation."]
|
|
28
|
-
end
|
|
26
|
+
return ["json-schema gem not installed. Add 'json-schema' to Gemfile for schema validation."] unless defined?(JSON::Validator)
|
|
29
27
|
|
|
30
28
|
errors = []
|
|
31
29
|
|
|
32
30
|
# Load the schema
|
|
33
31
|
schema = load_schema(options[:schema_path])
|
|
34
|
-
return [
|
|
32
|
+
return ['Failed to load schema'] unless schema
|
|
35
33
|
|
|
36
34
|
# Validate against JSON schema
|
|
37
35
|
begin
|
|
@@ -71,9 +69,9 @@ module Ukiryu
|
|
|
71
69
|
def default_schema_path
|
|
72
70
|
# Schema is in the sibling 'schema' directory at the same level as the gem
|
|
73
71
|
# From lib/ukiryu/, we go up to gem root, then to sibling schema/
|
|
74
|
-
gem_root = File.expand_path(
|
|
75
|
-
schema_dir = File.expand_path(
|
|
76
|
-
schema_file = File.join(schema_dir,
|
|
72
|
+
gem_root = File.expand_path('../..', __dir__) # ukiryu gem root
|
|
73
|
+
schema_dir = File.expand_path('../schema', gem_root) # src/ukiryu/schema/
|
|
74
|
+
schema_file = File.join(schema_dir, 'tool-profile.schema.yaml')
|
|
77
75
|
return schema_file if File.exist?(schema_file)
|
|
78
76
|
|
|
79
77
|
nil
|
data/lib/ukiryu/shell/bash.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative 'base'
|
|
4
4
|
|
|
5
5
|
module Ukiryu
|
|
6
6
|
module Shell
|
|
@@ -46,14 +46,14 @@ module Ukiryu
|
|
|
46
46
|
# @param args [Array<String>] the arguments
|
|
47
47
|
# @return [String] the complete command line
|
|
48
48
|
def join(executable, *args)
|
|
49
|
-
[executable, *args.map { |a| quote(a) }].join(
|
|
49
|
+
[executable, *args.map { |a| quote(a) }].join(' ')
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
# Get headless environment (disable DISPLAY on Unix)
|
|
53
53
|
#
|
|
54
54
|
# @return [Hash] environment variables for headless operation
|
|
55
55
|
def headless_environment
|
|
56
|
-
{
|
|
56
|
+
{ 'DISPLAY' => '' }
|
|
57
57
|
end
|
|
58
58
|
end
|
|
59
59
|
end
|
data/lib/ukiryu/shell/cmd.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative 'base'
|
|
4
4
|
|
|
5
5
|
module Ukiryu
|
|
6
6
|
module Shell
|
|
@@ -19,7 +19,7 @@ module Ukiryu
|
|
|
19
19
|
# @param string [String] the string to escape
|
|
20
20
|
# @return [String] the escaped string
|
|
21
21
|
def escape(string)
|
|
22
|
-
string.to_s.gsub(/[%^<>&|]/) {
|
|
22
|
+
string.to_s.gsub(/[%^<>&|]/) { '^$&' }
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# Quote an argument for cmd.exe
|
|
@@ -44,7 +44,7 @@ module Ukiryu
|
|
|
44
44
|
# @param path [String] the file path
|
|
45
45
|
# @return [String] the formatted path
|
|
46
46
|
def format_path(path)
|
|
47
|
-
path.to_s.gsub(
|
|
47
|
+
path.to_s.gsub('/', '\\')
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
# Format an environment variable reference
|
|
@@ -61,7 +61,7 @@ module Ukiryu
|
|
|
61
61
|
# @param args [Array<String>] the arguments
|
|
62
62
|
# @return [String] the complete command line
|
|
63
63
|
def join(executable, *args)
|
|
64
|
-
[executable, *args.map { |a| quote(a) }].join(
|
|
64
|
+
[executable, *args.map { |a| quote(a) }].join(' ')
|
|
65
65
|
end
|
|
66
66
|
|
|
67
67
|
# cmd.exe doesn't need DISPLAY variable
|
data/lib/ukiryu/shell/fish.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative 'base'
|
|
4
4
|
|
|
5
5
|
module Ukiryu
|
|
6
6
|
module Shell
|
|
@@ -20,7 +20,7 @@ module Ukiryu
|
|
|
20
20
|
# @param string [String] the string to escape
|
|
21
21
|
# @return [String] the escaped string
|
|
22
22
|
def escape(string)
|
|
23
|
-
string.to_s.gsub(/[`"$]/) {
|
|
23
|
+
string.to_s.gsub(/[`"$]/) { '`$&' }
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
# Quote an argument for PowerShell
|
|
@@ -46,7 +46,7 @@ module Ukiryu
|
|
|
46
46
|
# @param args [Array<String>] the arguments
|
|
47
47
|
# @return [String] the complete command line
|
|
48
48
|
def join(executable, *args)
|
|
49
|
-
[executable, *args.map { |a| quote(a) }].join(
|
|
49
|
+
[executable, *args.map { |a| quote(a) }].join(' ')
|
|
50
50
|
end
|
|
51
51
|
|
|
52
52
|
# PowerShell doesn't need DISPLAY variable
|
data/lib/ukiryu/shell/sh.rb
CHANGED
data/lib/ukiryu/shell/zsh.rb
CHANGED
data/lib/ukiryu/shell.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative
|
|
3
|
+
require_relative 'shell/base'
|
|
4
4
|
|
|
5
5
|
module Ukiryu
|
|
6
6
|
# Shell detection and management
|
|
@@ -8,10 +8,84 @@ module Ukiryu
|
|
|
8
8
|
# Provides EXPLICIT shell detection with no fallbacks.
|
|
9
9
|
# If shell cannot be determined, raises a clear error.
|
|
10
10
|
module Shell
|
|
11
|
+
# All supported shell types
|
|
12
|
+
VALID_SHELLS = %i[bash zsh fish sh powershell cmd].freeze
|
|
13
|
+
|
|
14
|
+
# Platform-specific shell mappings
|
|
15
|
+
UNIX_SHELLS = %i[bash zsh fish sh].freeze
|
|
16
|
+
WINDOWS_SHELLS = %i[powershell cmd bash].freeze
|
|
17
|
+
|
|
11
18
|
class << self
|
|
12
19
|
# Get or set the current shell (for explicit configuration)
|
|
13
20
|
attr_writer :current_shell
|
|
14
21
|
|
|
22
|
+
# Check if a shell symbol is valid
|
|
23
|
+
#
|
|
24
|
+
# @param shell_sym [Symbol] the shell symbol to check
|
|
25
|
+
# @return [Boolean] true if shell is valid
|
|
26
|
+
def valid?(shell_sym)
|
|
27
|
+
VALID_SHELLS.include?(shell_sym&.to_sym)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Get list of all valid shells
|
|
31
|
+
#
|
|
32
|
+
# @return [Array<Symbol>] list of valid shell symbols
|
|
33
|
+
def all_valid
|
|
34
|
+
VALID_SHELLS.dup
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Get shells valid for current platform
|
|
38
|
+
#
|
|
39
|
+
# @return [Array<Symbol>] list of valid shells for current platform
|
|
40
|
+
def valid_for_platform
|
|
41
|
+
Platform.windows? ? WINDOWS_SHELLS.dup : UNIX_SHELLS.dup
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Convert string to shell symbol
|
|
45
|
+
#
|
|
46
|
+
# @param str [String] the shell name string
|
|
47
|
+
# @return [Symbol] the shell symbol
|
|
48
|
+
# @raise [ArgumentError] if shell name is invalid
|
|
49
|
+
def from_string(str)
|
|
50
|
+
shell_sym = str.to_s.downcase.to_sym
|
|
51
|
+
return shell_sym if valid?(shell_sym)
|
|
52
|
+
|
|
53
|
+
raise ArgumentError,
|
|
54
|
+
"Invalid shell: #{str}. Valid shells: #{VALID_SHELLS.join(', ')}"
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Check if a shell is available on the system
|
|
58
|
+
#
|
|
59
|
+
# @param shell_sym [Symbol] the shell to check
|
|
60
|
+
# @return [Boolean] true if shell is available
|
|
61
|
+
def available?(shell_sym)
|
|
62
|
+
return false unless valid?(shell_sym)
|
|
63
|
+
|
|
64
|
+
case shell_sym
|
|
65
|
+
when :bash
|
|
66
|
+
shell_available_on_unix?('bash') || bash_available_on_windows?
|
|
67
|
+
when :zsh
|
|
68
|
+
shell_available_on_unix?('zsh')
|
|
69
|
+
when :fish
|
|
70
|
+
shell_available_on_unix?('fish')
|
|
71
|
+
when :sh
|
|
72
|
+
shell_available_on_unix?('sh')
|
|
73
|
+
when :powershell
|
|
74
|
+
powershell_available?
|
|
75
|
+
when :cmd
|
|
76
|
+
true # cmd is always available on Windows
|
|
77
|
+
else
|
|
78
|
+
false
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Get all shells available on the current system
|
|
83
|
+
#
|
|
84
|
+
# @return [Array<Symbol>] list of available shells
|
|
85
|
+
def available_shells
|
|
86
|
+
VALID_SHELLS.select { |shell| available?(shell) }
|
|
87
|
+
end
|
|
88
|
+
|
|
15
89
|
# Detect the current shell
|
|
16
90
|
#
|
|
17
91
|
# @return [Symbol] :bash, :zsh, :fish, :sh, :powershell, or :cmd
|
|
@@ -54,22 +128,22 @@ module Ukiryu
|
|
|
54
128
|
def class_for(name)
|
|
55
129
|
case name
|
|
56
130
|
when :bash
|
|
57
|
-
require_relative
|
|
131
|
+
require_relative 'shell/bash'
|
|
58
132
|
Bash
|
|
59
133
|
when :zsh
|
|
60
|
-
require_relative
|
|
134
|
+
require_relative 'shell/zsh'
|
|
61
135
|
Zsh
|
|
62
136
|
when :fish
|
|
63
|
-
require_relative
|
|
137
|
+
require_relative 'shell/fish'
|
|
64
138
|
Fish
|
|
65
139
|
when :sh
|
|
66
|
-
require_relative
|
|
140
|
+
require_relative 'shell/sh'
|
|
67
141
|
Sh
|
|
68
142
|
when :powershell
|
|
69
|
-
require_relative
|
|
143
|
+
require_relative 'shell/powershell'
|
|
70
144
|
PowerShell
|
|
71
145
|
when :cmd
|
|
72
|
-
require_relative
|
|
146
|
+
require_relative 'shell/cmd'
|
|
73
147
|
Cmd
|
|
74
148
|
else
|
|
75
149
|
raise UnknownShellError, "Unknown shell: #{name}"
|
|
@@ -83,13 +157,13 @@ module Ukiryu
|
|
|
83
157
|
# @return [Symbol] detected shell
|
|
84
158
|
def detect_windows_shell
|
|
85
159
|
# PowerShell check
|
|
86
|
-
return :powershell if ENV[
|
|
160
|
+
return :powershell if ENV['PSModulePath']
|
|
87
161
|
|
|
88
162
|
# Git Bash / MSYS check
|
|
89
|
-
return :bash if ENV[
|
|
163
|
+
return :bash if ENV['MSYSTEM'] || ENV['MINGW_PREFIX']
|
|
90
164
|
|
|
91
165
|
# WSL check
|
|
92
|
-
return :bash if ENV[
|
|
166
|
+
return :bash if ENV['WSL_DISTRO']
|
|
93
167
|
|
|
94
168
|
# Default to cmd on Windows
|
|
95
169
|
:cmd
|
|
@@ -99,37 +173,36 @@ module Ukiryu
|
|
|
99
173
|
#
|
|
100
174
|
# @return [Symbol] detected shell
|
|
101
175
|
def detect_unix_shell
|
|
102
|
-
shell_env = ENV[
|
|
176
|
+
shell_env = ENV['SHELL']
|
|
103
177
|
|
|
104
178
|
# Try to determine from SHELL environment variable
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
else
|
|
123
|
-
# Unknown shell in ENV - check if executable
|
|
124
|
-
if File.executable?(shell_env)
|
|
125
|
-
# Return as symbol for custom shell
|
|
126
|
-
shell_name.to_sym
|
|
127
|
-
else
|
|
128
|
-
raise UnknownShellError, unknown_shell_error_msg("Unknown shell in SHELL: #{shell_env}")
|
|
129
|
-
end
|
|
130
|
-
end
|
|
179
|
+
raise UnknownShellError, unknown_shell_error_msg('SHELL environment variable not set') unless shell_env
|
|
180
|
+
return :bash if shell_env.end_with?('bash')
|
|
181
|
+
return :zsh if shell_env.end_with?('zsh')
|
|
182
|
+
return :fish if shell_env.end_with?('fish')
|
|
183
|
+
return :sh if shell_env.end_with?('sh')
|
|
184
|
+
|
|
185
|
+
# Try to determine from executable name
|
|
186
|
+
shell_name = File.basename(shell_env)
|
|
187
|
+
case shell_name
|
|
188
|
+
when 'bash'
|
|
189
|
+
:bash
|
|
190
|
+
when 'zsh'
|
|
191
|
+
:zsh
|
|
192
|
+
when 'fish'
|
|
193
|
+
:fish
|
|
194
|
+
when 'sh'
|
|
195
|
+
:sh
|
|
131
196
|
else
|
|
132
|
-
|
|
197
|
+
# Unknown shell in ENV - check if executable
|
|
198
|
+
unless File.executable?(shell_env)
|
|
199
|
+
raise UnknownShellError,
|
|
200
|
+
unknown_shell_error_msg("Unknown shell in SHELL: #{shell_env}")
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Return as symbol for custom shell
|
|
204
|
+
shell_name.to_sym
|
|
205
|
+
|
|
133
206
|
end
|
|
134
207
|
end
|
|
135
208
|
|
|
@@ -154,11 +227,45 @@ module Ukiryu
|
|
|
154
227
|
end
|
|
155
228
|
|
|
156
229
|
Current environment:
|
|
157
|
-
Platform: #{
|
|
230
|
+
Platform: #{RUBY_PLATFORM}
|
|
158
231
|
SHELL: #{ENV['SHELL']}
|
|
159
232
|
PSModulePath: #{ENV['PSModulePath']}
|
|
160
233
|
ERROR
|
|
161
234
|
end
|
|
235
|
+
|
|
236
|
+
# Check if a Unix shell is available on the system
|
|
237
|
+
#
|
|
238
|
+
# @param shell_name [String] the shell executable name
|
|
239
|
+
# @return [Boolean] true if shell is available
|
|
240
|
+
def shell_available_on_unix?(shell_name)
|
|
241
|
+
return false if Platform.windows?
|
|
242
|
+
|
|
243
|
+
# Check if shell is in PATH
|
|
244
|
+
system("which #{shell_name} > /dev/null 2>&1")
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Check if bash is available on Windows (Git Bash/MSYS)
|
|
248
|
+
#
|
|
249
|
+
# @return [Boolean] true if bash is available
|
|
250
|
+
def bash_available_on_windows?
|
|
251
|
+
return false unless Platform.windows?
|
|
252
|
+
|
|
253
|
+
# Check for Git Bash / MSYS
|
|
254
|
+
!!(ENV['MSYSTEM'] || ENV['MINGW_PREFIX'] || ENV['WSL_DISTRO'] ||
|
|
255
|
+
system('where bash >nul 2>&1'))
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# Check if PowerShell is available
|
|
259
|
+
#
|
|
260
|
+
# @return [Boolean] true if PowerShell is available
|
|
261
|
+
def powershell_available?
|
|
262
|
+
return true if Platform.windows? && ENV['PSModulePath']
|
|
263
|
+
|
|
264
|
+
# On Unix, check for PowerShell Core (pwsh)
|
|
265
|
+
return true if !Platform.windows? && system('which pwsh > /dev/null 2>&1')
|
|
266
|
+
|
|
267
|
+
false
|
|
268
|
+
end
|
|
162
269
|
end
|
|
163
270
|
end
|
|
164
271
|
end
|