srsh 0.8.0 → 1.0.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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +12 -3
  3. data/README.md +446 -8
  4. data/bin/srsh +71 -0
  5. data/docs/assets/slut.txt +4 -0
  6. data/docs/assets/srsh-mark.svg +12 -0
  7. data/docs/css/style.css +696 -0
  8. data/docs/index.html +703 -0
  9. data/docs/js/app.js +203 -0
  10. data/examples/bridge.rsh +8 -0
  11. data/examples/calculator.rsh +253 -0
  12. data/examples/defer.rsh +14 -0
  13. data/examples/hot.rsh +14 -0
  14. data/examples/meta.rsh +20 -0
  15. data/examples/modules/text.rsh +6 -0
  16. data/examples/modules.rsh +6 -0
  17. data/examples/paste.rsh +15 -0
  18. data/examples/plugin.rb +8 -0
  19. data/examples/power.rsh +65 -0
  20. data/examples/tour.rsh +38 -0
  21. data/ext/srsh_native/extconf.rb +3 -0
  22. data/ext/srsh_native/srsh_native.c +48 -0
  23. data/language-docs/LANGUAGE.md +670 -0
  24. data/language-docs/MIGRATION.md +44 -0
  25. data/language-docs/SECURITY.md +44 -0
  26. data/lib/srsh/app.rb +261 -0
  27. data/lib/srsh/builtins.rb +492 -0
  28. data/lib/srsh/editor.rb +530 -0
  29. data/lib/srsh/errors.rb +23 -0
  30. data/lib/srsh/history.rb +74 -0
  31. data/lib/srsh/language/evaluator.rb +1175 -0
  32. data/lib/srsh/language/lexer.rb +316 -0
  33. data/lib/srsh/language/parser.rb +997 -0
  34. data/lib/srsh/language/token.rb +5 -0
  35. data/lib/srsh/language/values.rb +392 -0
  36. data/lib/srsh/paths.rb +29 -0
  37. data/lib/srsh/plugins.rb +59 -0
  38. data/lib/srsh/process_identity.rb +38 -0
  39. data/lib/srsh/security.rb +38 -0
  40. data/lib/srsh/shell/executor.rb +1182 -0
  41. data/lib/srsh/shell/job.rb +101 -0
  42. data/lib/srsh/shell/lexer.rb +114 -0
  43. data/lib/srsh/shell/terminal.rb +26 -0
  44. data/lib/srsh/state.rb +136 -0
  45. data/lib/srsh/theme.rb +108 -0
  46. data/lib/srsh/version.rb +3 -0
  47. data/lib/srsh.rb +11 -5
  48. metadata +61 -14
  49. data/exe/srsh +0 -6
  50. data/lib/srsh/runner.rb +0 -2416
@@ -0,0 +1,101 @@
1
+ module Srsh
2
+ module Shell
3
+ class Job
4
+ WAIT_FLAGS = begin
5
+ flags = Process::WNOHANG | Process::WUNTRACED
6
+ flags |= Process.const_get(:WCONTINUED) if Process.const_defined?(:WCONTINUED)
7
+ flags
8
+ end
9
+
10
+ attr_accessor :id, :notified
11
+ attr_reader :pgid, :pids, :command, :status
12
+
13
+ def initialize(pgid:, pids:, command:, background:)
14
+ @pgid = pgid
15
+ @pids = pids.freeze
16
+ @command = command
17
+ @background = background
18
+ @pid_states = pids.to_h { |pid| [pid, :running] }
19
+ @status = :running
20
+ @notified = false
21
+ end
22
+
23
+ def background? = @background
24
+ def running? = @status == :running
25
+ def stopped? = @status == :stopped
26
+ def done? = @status == :done
27
+
28
+ def mark_background! = @background = true
29
+ def mark_foreground! = @background = false
30
+
31
+ def mark_running!
32
+ @pid_states.each_key do |pid|
33
+ @pid_states[pid] = :running unless @pid_states[pid] == :done
34
+ end
35
+ recalculate!
36
+ end
37
+
38
+ def mark_stopped!(pid = nil)
39
+ if pid
40
+ @pid_states[pid] = :stopped unless @pid_states[pid] == :done
41
+ else
42
+ @pid_states.each_key { |p| @pid_states[p] = :stopped unless @pid_states[p] == :done }
43
+ end
44
+ recalculate!
45
+ end
46
+
47
+ def observe(pid, process_status)
48
+ return self unless @pid_states.key?(pid)
49
+ if process_status.stopped?
50
+ @pid_states[pid] = :stopped
51
+ elsif process_status.exited? || process_status.signaled?
52
+ @pid_states[pid] = :done
53
+ elsif process_status.respond_to?(:continued?) && process_status.continued?
54
+ @pid_states[pid] = :running
55
+ end
56
+ recalculate!
57
+ end
58
+
59
+ def refresh!
60
+ return self if done?
61
+
62
+ @pids.each do |pid|
63
+ next if @pid_states[pid] == :done
64
+ begin
65
+ result = Process.waitpid2(pid, WAIT_FLAGS)
66
+ observe(*result) if result
67
+ rescue Errno::ECHILD
68
+ # No waitable child means it was reaped by the synchronous path or
69
+ # elsewhere in srsh. Treat it as terminal, never as magically live.
70
+ @pid_states[pid] = :done
71
+ rescue Errno::EINVAL
72
+ # Some libc/Ruby combinations expose a wait flag constant but reject
73
+ # the combination at runtime. Retry with the universally portable set.
74
+ begin
75
+ result = Process.waitpid2(pid, Process::WNOHANG | Process::WUNTRACED)
76
+ observe(*result) if result
77
+ rescue Errno::ECHILD
78
+ @pid_states[pid] = :done
79
+ end
80
+ end
81
+ end
82
+
83
+ recalculate!
84
+ end
85
+
86
+ private
87
+
88
+ def recalculate!
89
+ states = @pid_states.values
90
+ @status = if states.all? { |s| s == :done }
91
+ :done
92
+ elsif states.none? { |s| s == :running } && states.any? { |s| s == :stopped }
93
+ :stopped
94
+ else
95
+ :running
96
+ end
97
+ self
98
+ end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,114 @@
1
+ require_relative '../errors'
2
+
3
+ module Srsh
4
+ module Shell
5
+ Lexeme = Data.define(:type, :text)
6
+
7
+ class Lexer
8
+ OPERATORS = %w[2>> 2> >> && || | & ; > <].freeze
9
+ MAX_INPUT = 1024 * 1024
10
+ MAX_LEXEMES = 100_000
11
+
12
+ def self.scan(input)
13
+ new(input).scan
14
+ end
15
+
16
+ def initialize(input)
17
+ @s = input.to_s
18
+ raise ParseError, 'command line too large' if @s.bytesize > MAX_INPUT
19
+ end
20
+
21
+ def scan
22
+ out = []
23
+ buf = +''
24
+ quote = nil
25
+ escaped = false
26
+ subst_depth = 0
27
+ i = 0
28
+
29
+ flush = lambda do
30
+ unless buf.empty?
31
+ raise ParseError, 'command has too many tokens' if out.length >= MAX_LEXEMES
32
+ out << Lexeme.new(:word, buf)
33
+ buf = +''
34
+ end
35
+ end
36
+
37
+ while i < @s.length
38
+ c = @s[i]
39
+
40
+ if escaped
41
+ buf << c
42
+ escaped = false
43
+ i += 1
44
+ next
45
+ end
46
+
47
+ if c == '\\'
48
+ buf << c
49
+ escaped = true
50
+ i += 1
51
+ next
52
+ end
53
+
54
+ if quote
55
+ buf << c
56
+ quote = nil if c == quote
57
+ i += 1
58
+ next
59
+ end
60
+
61
+ if c == "'" || c == '"'
62
+ quote = c
63
+ buf << c
64
+ i += 1
65
+ next
66
+ end
67
+
68
+ if c == '$' && @s[i + 1] == '('
69
+ subst_depth += 1
70
+ buf << '$('
71
+ i += 2
72
+ next
73
+ elsif c == '(' && subst_depth.positive?
74
+ subst_depth += 1
75
+ buf << c
76
+ i += 1
77
+ next
78
+ elsif c == ')' && subst_depth.positive?
79
+ subst_depth -= 1
80
+ buf << c
81
+ i += 1
82
+ next
83
+ end
84
+
85
+ if subst_depth.zero? && c.match?(/\s/)
86
+ flush.call
87
+ i += 1
88
+ next
89
+ end
90
+
91
+ if subst_depth.zero?
92
+ op = OPERATORS.find { |candidate| @s[i, candidate.length] == candidate }
93
+ if op
94
+ flush.call
95
+ raise ParseError, 'command has too many tokens' if out.length >= MAX_LEXEMES
96
+ out << Lexeme.new(:op, op)
97
+ i += op.length
98
+ next
99
+ end
100
+ end
101
+
102
+ buf << c
103
+ i += 1
104
+ end
105
+
106
+ raise IncompleteInput, 'trailing backslash' if escaped
107
+ raise IncompleteInput, 'unterminated quote' if quote
108
+ raise IncompleteInput, 'unterminated command substitution' unless subst_depth.zero?
109
+ flush.call
110
+ out
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,26 @@
1
+ module Srsh
2
+ module Shell
3
+ module Terminal
4
+ module_function
5
+
6
+ begin
7
+ require 'fiddle/import'
8
+ module LibC
9
+ extend Fiddle::Importer
10
+ dlload Fiddle.dlopen(nil)
11
+ extern 'int tcsetpgrp(int, int)'
12
+ end
13
+ AVAILABLE = true
14
+ rescue LoadError, Fiddle::DLError
15
+ AVAILABLE = false
16
+ end
17
+
18
+ def foreground(pgid, io = STDIN)
19
+ return false unless AVAILABLE && io.tty?
20
+ LibC.tcsetpgrp(io.fileno, pgid.to_i).zero?
21
+ rescue SystemCallError, Fiddle::DLError
22
+ false
23
+ end
24
+ end
25
+ end
26
+ end
data/lib/srsh/state.rb ADDED
@@ -0,0 +1,136 @@
1
+ module Srsh
2
+ class State
3
+ attr_accessor :theme_name
4
+ attr_reader :aliases, :functions, :prototypes, :traits, :hooks, :jobs, :owner_thread, :options
5
+
6
+ def initialize
7
+ @owner_thread = Thread.current
8
+ @last_status = 0
9
+ @last_bg_pid = nil
10
+ @theme_name = 'classic'
11
+ @options = { 'pipefail' => false, 'nounset' => false, 'noclobber' => false }
12
+ @aliases = {}
13
+ @functions = {}
14
+ @prototypes = {}
15
+ @traits = {}
16
+ @hooks = Hash.new { |h, k| h[k] = [] }
17
+ @root_locals = {}
18
+ @owner_locals = [@root_locals]
19
+ @scope_key = :"srsh_scopes_#{object_id}"
20
+ @status_key = :"srsh_status_#{object_id}"
21
+ @bg_key = :"srsh_bg_#{object_id}"
22
+ @jobs = []
23
+ @jobs_lock = Mutex.new
24
+ @next_job_id = 1
25
+ end
26
+
27
+ # RSH threads share global definitions and the root interactive scope for
28
+ # reads, but each OS thread gets its own scope stack. Function/lambda calls
29
+ # therefore cannot stomp another thread's locals while closures still see
30
+ # the snapshot they captured.
31
+ def locals
32
+ return @owner_locals if Thread.current == @owner_thread
33
+ Thread.current[@scope_key] ||= [@root_locals]
34
+ end
35
+
36
+ def push_scope(seed = {}) = locals << seed
37
+
38
+ def pop_scope
39
+ stack = locals
40
+ stack.pop if stack.length > 1
41
+ end
42
+
43
+ def local_get(name)
44
+ locals.reverse_each { |scope| return scope[name] if scope.key?(name) }
45
+ nil
46
+ end
47
+
48
+ def local_defined?(name) = locals.reverse_each.any? { |s| s.key?(name) }
49
+
50
+ def locals_snapshot
51
+ locals.each_with_object({}) { |scope, merged| merged.merge!(scope) }
52
+ end
53
+
54
+ # `:=` always binds in the current lexical/function scope. Compound
55
+ # assignment walks outward to the nearest existing binding.
56
+ def local_define(name, value) = locals.last[name] = value
57
+
58
+ def local_set(name, value)
59
+ stack = locals
60
+ if worker_thread?
61
+ # Never let an ordinary compound assignment in a worker mutate the
62
+ # shared root REPL/script scope. If the binding only exists globally,
63
+ # shadow it in the task's current scope instead.
64
+ scope = stack[1..].to_a.reverse.find { |s| s.key?(name) } || stack.last
65
+ else
66
+ scope = stack.reverse.find { |s| s.key?(name) } || stack.last
67
+ end
68
+ scope[name] = value
69
+ end
70
+
71
+ def last_status
72
+ return @last_status if Thread.current == @owner_thread
73
+ Thread.current[@status_key].nil? ? @last_status : Thread.current[@status_key]
74
+ end
75
+
76
+ def last_status=(value)
77
+ if Thread.current == @owner_thread
78
+ @last_status = value
79
+ else
80
+ Thread.current[@status_key] = value
81
+ end
82
+ end
83
+
84
+ def last_bg_pid
85
+ return @last_bg_pid if Thread.current == @owner_thread
86
+ Thread.current[@bg_key].nil? ? @last_bg_pid : Thread.current[@bg_key]
87
+ end
88
+
89
+ def last_bg_pid=(value)
90
+ if Thread.current == @owner_thread
91
+ @last_bg_pid = value
92
+ else
93
+ Thread.current[@bg_key] = value
94
+ end
95
+ end
96
+
97
+ def worker_thread? = Thread.current != @owner_thread
98
+
99
+ def add_job(job)
100
+ @jobs_lock.synchronize do
101
+ job.id = @next_job_id
102
+ @next_job_id += 1
103
+ @jobs << job
104
+ end
105
+ job
106
+ end
107
+
108
+ def jobs_snapshot = @jobs_lock.synchronize { @jobs.dup }
109
+
110
+ def prune_jobs!
111
+ snapshot = jobs_snapshot
112
+ snapshot.each do |job|
113
+ next if job.done?
114
+ begin
115
+ job.refresh!
116
+ rescue StandardError => e
117
+ # Job bookkeeping is housekeeping. A platform-specific wait quirk must
118
+ # never tear down the user's interactive shell.
119
+ warn "srsh: job refresh failed for [#{job.id || '?'}]: #{e.class}: #{e.message}"
120
+ end
121
+ end
122
+ @jobs_lock.synchronize { @jobs.reject! { |job| job.done? && job.notified } }
123
+ end
124
+
125
+ def hook(type, &block) = @hooks[type.to_sym] << block
126
+ def clear_hooks! = @hooks.clear
127
+
128
+ def run_hooks(type, *args)
129
+ @hooks[type.to_sym].each do |block|
130
+ block.call(*args)
131
+ rescue StandardError => e
132
+ warn "srsh hook #{type}: #{e.class}: #{e.message}"
133
+ end
134
+ end
135
+ end
136
+ end
data/lib/srsh/theme.rb ADDED
@@ -0,0 +1,108 @@
1
+ require 'json'
2
+ require_relative 'security'
3
+
4
+ module Srsh
5
+ class Theme
6
+ MAX_THEME_BYTES = 256 * 1024
7
+ ANSI_RE = /\A(?:\d{1,3})(?:;\d{1,3}){0,6}\z/
8
+
9
+ DEFAULTS = {
10
+ 'classic' => {
11
+ border: '1;35', title: '1;33', key: '1;36', value: '0;37',
12
+ ok: '32', warn: '33', error: '31', dim: '90', path: '33', host: '36', mark: '35'
13
+ },
14
+ 'mono' => {
15
+ border: '1;37', title: '1;37', key: '0;37', value: '0;37',
16
+ ok: '0;37', warn: '0;37', error: '0;37', dim: '90', path: '0;37', host: '0;37', mark: '0;37'
17
+ },
18
+ 'neon' => {
19
+ border: '1;35', title: '1;92', key: '1;95', value: '0;37',
20
+ ok: '1;92', warn: '1;93', error: '1;91', dim: '90', path: '1;93', host: '1;96', mark: '1;95'
21
+ },
22
+ 'ocean' => {
23
+ border: '1;34', title: '1;96', key: '36', value: '0;37',
24
+ ok: '1;92', warn: '1;93', error: '1;91', dim: '90', path: '34', host: '96', mark: '36'
25
+ }
26
+ }.freeze
27
+
28
+ attr_reader :name
29
+
30
+ def initialize(paths, state)
31
+ @paths = paths
32
+ @state = state
33
+ @themes = DEFAULTS.transform_values(&:dup)
34
+ load_user_themes
35
+ @base_themes = @themes.transform_values(&:dup).freeze
36
+ wanted = ENV['SRSH_THEME'].to_s.strip
37
+ wanted = File.read(paths.theme_state).strip if wanted.empty? && File.file?(paths.theme_state)
38
+ use(wanted.empty? ? 'classic' : wanted)
39
+ end
40
+
41
+ def names = @themes.keys.sort
42
+
43
+ def reset_dynamic!
44
+ current = @name
45
+ @themes = @base_themes.transform_values(&:dup)
46
+ use(@themes.key?(current) ? current : 'classic')
47
+ end
48
+
49
+ def register(name, values)
50
+ clean = {}
51
+ values.each do |key, value|
52
+ value = value.to_s
53
+ clean[key.to_sym] = value if value.match?(ANSI_RE)
54
+ end
55
+ @themes[name.to_s] = @themes['classic'].merge(clean) unless clean.empty?
56
+ end
57
+
58
+ def use(name)
59
+ return false unless @themes.key?(name)
60
+ @name = name
61
+ @state.theme_name = name
62
+ Security.atomic_write(@paths.theme_state, "#{name}\n") rescue nil
63
+ true
64
+ end
65
+
66
+ def code(key) = @themes.fetch(@name, @themes['classic'])[key.to_sym]
67
+
68
+ def paint(text, key, io: STDOUT)
69
+ code = code(key)
70
+ color_ok = io.respond_to?(:tty?) && io.tty?
71
+ return text.to_s if !color_ok || code.nil?
72
+ "\e[#{code}m#{text}\e[0m"
73
+ end
74
+
75
+ private
76
+
77
+ def load_user_themes
78
+ Dir.glob(File.join(@paths.themes, '*.{theme,json}')).sort.each do |path|
79
+ next unless Security.private_regular_file?(path)
80
+ next if File.size(path) > MAX_THEME_BYTES
81
+ raw = File.extname(path) == '.json' ? JSON.parse(File.binread(path, MAX_THEME_BYTES + 1)) : parse_kv(path)
82
+ next unless raw.is_a?(Hash)
83
+ clean = {}
84
+ raw.each do |key, value|
85
+ value = value.to_s
86
+ clean[key.to_sym] = value if value.match?(ANSI_RE)
87
+ end
88
+ next if clean.empty?
89
+ @themes[File.basename(path, '.*')] = @themes['classic'].merge(clean)
90
+ rescue JSON::ParserError, SystemCallError
91
+ next
92
+ end
93
+ end
94
+
95
+ def parse_kv(path)
96
+ data = File.binread(path, MAX_THEME_BYTES + 1)
97
+ return {} if data.bytesize > MAX_THEME_BYTES
98
+ data.force_encoding(Encoding::UTF_8)
99
+ return {} unless data.valid_encoding?
100
+ data.each_line.filter_map do |line|
101
+ line = line.strip
102
+ next if line.empty? || line.start_with?('#')
103
+ key, value = line.split('=', 2)
104
+ [key&.strip, value&.strip] if key && value
105
+ end.to_h
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,3 @@
1
+ module Srsh
2
+ VERSION = '1.0.0'
3
+ end
data/lib/srsh.rb CHANGED
@@ -1,9 +1,15 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "srsh/runner"
4
-
5
- module Srsh
6
- def self.run(argv = ARGV)
7
- Runner.run(argv)
3
+ begin
4
+ require 'srsh_native'
5
+ rescue LoadError
6
+ begin
7
+ require_relative '../ext/srsh_native/srsh_native'
8
+ rescue LoadError
9
+ # Optional: the complete shell remains Ruby-only when this is absent.
8
10
  end
9
11
  end
12
+
13
+ require_relative 'srsh/app'
14
+
15
+ require_relative 'srsh/process_identity'
metadata CHANGED
@@ -1,32 +1,78 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: srsh
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - RobertFlexx
8
- bindir: exe
8
+ autorequire:
9
+ bindir: bin
9
10
  cert_chain: []
10
- date: 1980-01-02 00:00:00.000000000 Z
11
+ date: 2026-09-02 00:00:00.000000000 Z
11
12
  dependencies: []
12
- description: srsh is a Ruby-based interactive shell featuring a custom scripting language
13
- (RSH), themes, plugins, and advanced line editing.
13
+ description: A Unix shell written in Ruby with a structured scripting language for
14
+ shell programs.
14
15
  email:
15
- - ''
16
16
  executables:
17
17
  - srsh
18
- extensions: []
18
+ extensions:
19
+ - ext/srsh_native/extconf.rb
19
20
  extra_rdoc_files: []
20
21
  files:
21
22
  - LICENSE
22
23
  - README.md
23
- - exe/srsh
24
+ - bin/srsh
25
+ - docs/assets/slut.txt
26
+ - docs/assets/srsh-mark.svg
27
+ - docs/css/style.css
28
+ - docs/index.html
29
+ - docs/js/app.js
30
+ - examples/bridge.rsh
31
+ - examples/calculator.rsh
32
+ - examples/defer.rsh
33
+ - examples/hot.rsh
34
+ - examples/meta.rsh
35
+ - examples/modules.rsh
36
+ - examples/modules/text.rsh
37
+ - examples/paste.rsh
38
+ - examples/plugin.rb
39
+ - examples/power.rsh
40
+ - examples/tour.rsh
41
+ - ext/srsh_native/extconf.rb
42
+ - ext/srsh_native/srsh_native.c
43
+ - language-docs/LANGUAGE.md
44
+ - language-docs/MIGRATION.md
45
+ - language-docs/SECURITY.md
24
46
  - lib/srsh.rb
25
- - lib/srsh/runner.rb
26
- homepage: https://github.com/RobertFlexx/srsh
47
+ - lib/srsh/app.rb
48
+ - lib/srsh/builtins.rb
49
+ - lib/srsh/editor.rb
50
+ - lib/srsh/errors.rb
51
+ - lib/srsh/history.rb
52
+ - lib/srsh/language/evaluator.rb
53
+ - lib/srsh/language/lexer.rb
54
+ - lib/srsh/language/parser.rb
55
+ - lib/srsh/language/token.rb
56
+ - lib/srsh/language/values.rb
57
+ - lib/srsh/paths.rb
58
+ - lib/srsh/plugins.rb
59
+ - lib/srsh/process_identity.rb
60
+ - lib/srsh/security.rb
61
+ - lib/srsh/shell/executor.rb
62
+ - lib/srsh/shell/job.rb
63
+ - lib/srsh/shell/lexer.rb
64
+ - lib/srsh/shell/terminal.rb
65
+ - lib/srsh/state.rb
66
+ - lib/srsh/theme.rb
67
+ - lib/srsh/version.rb
68
+ homepage: https://github.com/RobertFlexx/RSH
27
69
  licenses:
28
70
  - MIT
29
- metadata: {}
71
+ metadata:
72
+ source_code_uri: https://github.com/RobertFlexx/RSH
73
+ bug_tracker_uri: https://github.com/RobertFlexx/RSH/issues
74
+ rubygems_mfa_required: 'true'
75
+ post_install_message:
30
76
  rdoc_options: []
31
77
  require_paths:
32
78
  - lib
@@ -34,14 +80,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
34
80
  requirements:
35
81
  - - ">="
36
82
  - !ruby/object:Gem::Version
37
- version: 3.1.0
83
+ version: '3.2'
38
84
  required_rubygems_version: !ruby/object:Gem::Requirement
39
85
  requirements:
40
86
  - - ">="
41
87
  - !ruby/object:Gem::Version
42
88
  version: '0'
43
89
  requirements: []
44
- rubygems_version: 3.7.2
90
+ rubygems_version: 3.4.19
91
+ signing_key:
45
92
  specification_version: 4
46
- summary: srsh - a simple Ruby shell with RSH scripting
93
+ summary: Simple Ruby Shell with the RSH scripting language
47
94
  test_files: []
data/exe/srsh DELETED
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env ruby
2
- # frozen_string_literal: true
3
-
4
- require "srsh"
5
-
6
- exit(Srsh.run(ARGV) || 0)