rash-command-shell 0.3.1 → 0.4.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 +4 -4
- data/bin/rash +1 -1
- data/lib/rash.rb +28 -1
- data/lib/rash/ext/filesystem.rb +40 -11
- data/lib/rash/jobcontrol.rb +1 -1
- data/lib/rash/pipeline.rb +98 -8
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 45f54bfa6b60f6a2a35a32d087ad1a722fb88f36fcd46933ae7145ee9dfc944d
|
4
|
+
data.tar.gz: 4dd605ed6fcbfb8a5fbacbd4ff11075933e3e6829785f7605442822f4e5fd508
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 55dde4dead963ce469de63b5c7490224d3d29ca406aafb3ef1d1fd0c2813d3433ba258ed433629fc7983cbe9a399560cf7dee912df871e86cf8c491f3ad83a3b
|
7
|
+
data.tar.gz: 5fe62b4dcc77acb7774f8b06360d4a4768b6b7bd63633886c40fbdbc7e0af41bd4240cbb1c867912ee11eec287e61f19f468ab9b354ac842346e5b46f6a9caea
|
data/bin/rash
CHANGED
@@ -4,7 +4,7 @@ if ARGV.empty?
|
|
4
4
|
exec("irb", "-r", "rash", *ARGV)
|
5
5
|
elsif ARGV[0] =~ /(-)?-v(ersion)?/
|
6
6
|
puts "Rash (c) 2020 Kellen Watt"
|
7
|
-
puts "Version 0.
|
7
|
+
puts "Version 0.4.0" # I may forget to update this
|
8
8
|
elsif File.exists?(ARGV[0]) && !File.directory?(ARGV[0])
|
9
9
|
require "rash"
|
10
10
|
file = ARGV.shift
|
data/lib/rash.rb
CHANGED
@@ -14,7 +14,21 @@ class Environment
|
|
14
14
|
ENV["OLDPWD"] = old.to_s
|
15
15
|
Dir.pwd
|
16
16
|
end
|
17
|
-
|
17
|
+
|
18
|
+
# Note that this works regardless of which version of chdir is used.
|
19
|
+
def push_dir(dir = nil)
|
20
|
+
@directory_stack.push(Dir.pwd)
|
21
|
+
self.chdir(dir)
|
22
|
+
end
|
23
|
+
|
24
|
+
def pop_dir
|
25
|
+
self.chdir(@directory_stack.pop) if @directory_stack.size > 0
|
26
|
+
end
|
27
|
+
|
28
|
+
def dirs
|
29
|
+
@directory_stack
|
30
|
+
end
|
31
|
+
|
18
32
|
def add_path(path)
|
19
33
|
ENV["PATH"] += File::PATH_SEPARATOR + (path.respond_to?(:path) ? path.path : path.to_s)
|
20
34
|
end
|
@@ -76,6 +90,8 @@ class Environment
|
|
76
90
|
|
77
91
|
@active_pipelines = []
|
78
92
|
|
93
|
+
@directory_stack = []
|
94
|
+
|
79
95
|
@prompt = {
|
80
96
|
AUTO_INDENT: true,
|
81
97
|
RETURN: ""
|
@@ -121,6 +137,17 @@ def cd(dir = nil)
|
|
121
137
|
$env.chdir(d)
|
122
138
|
end
|
123
139
|
|
140
|
+
def pushd(dir = nil)
|
141
|
+
case dir
|
142
|
+
when File, Dir
|
143
|
+
dir = dir.path if File.directory(dir.path)
|
144
|
+
end
|
145
|
+
$env.push_dir(dir)
|
146
|
+
end
|
147
|
+
|
148
|
+
def popd
|
149
|
+
$env.pop_dir
|
150
|
+
end
|
124
151
|
|
125
152
|
def run(file, *args)
|
126
153
|
filename = file.to_s
|
data/lib/rash/ext/filesystem.rb
CHANGED
@@ -16,20 +16,26 @@ class Environment
|
|
16
16
|
Dir.pwd
|
17
17
|
end
|
18
18
|
|
19
|
-
def local_def(name, &block)
|
20
|
-
@working_directory.add_local_method(name
|
19
|
+
def local_def(name, locked: false, &block)
|
20
|
+
@working_directory.add_local_method(name, &block)
|
21
|
+
@working_directory.lock_method(name) if locked
|
22
|
+
name.to_sym
|
21
23
|
end
|
22
24
|
|
23
25
|
def local_undef(name)
|
24
|
-
@working_directory.clear_local_method(name
|
26
|
+
@working_directory.clear_local_method(name)
|
25
27
|
end
|
26
28
|
|
27
29
|
def local_method?(name)
|
28
|
-
@working_directory.
|
30
|
+
@working_directory.local_method?(name)
|
31
|
+
end
|
32
|
+
|
33
|
+
def local_methods
|
34
|
+
@working_directory.local_methods
|
29
35
|
end
|
30
36
|
|
31
37
|
def local_call(name, *args, &block)
|
32
|
-
@working_directory.
|
38
|
+
@working_directory.local_method(name).call(*args, &block)
|
33
39
|
end
|
34
40
|
|
35
41
|
private
|
@@ -65,7 +71,6 @@ class Environment
|
|
65
71
|
end
|
66
72
|
|
67
73
|
class Directory
|
68
|
-
attr_reader :local_methods
|
69
74
|
attr_reader :parent, :children
|
70
75
|
|
71
76
|
def self.root(dir)
|
@@ -76,7 +81,31 @@ class Environment
|
|
76
81
|
@path = Dir.new(dir)
|
77
82
|
@parent = parent
|
78
83
|
@children = []
|
79
|
-
@local_methods = parent&.
|
84
|
+
@local_methods = parent&.unlocked_local_methods || {}
|
85
|
+
@locked_methods = []
|
86
|
+
end
|
87
|
+
|
88
|
+
def local_method(name)
|
89
|
+
@local_methods[name.to_sym]
|
90
|
+
end
|
91
|
+
|
92
|
+
def local_methods
|
93
|
+
@local_methods.keys
|
94
|
+
end
|
95
|
+
|
96
|
+
def unlocked_local_methods
|
97
|
+
@local_methods.filter{|k, v| !@locked_methods.include?(k)}
|
98
|
+
end
|
99
|
+
|
100
|
+
def lock_method(name)
|
101
|
+
n = name.to_sym
|
102
|
+
raise NameError.new("#{name} is not a local method", n) unless @local_methods.key?(n)
|
103
|
+
@locked_methods << n unless @locked_methods.include?(n)
|
104
|
+
n
|
105
|
+
end
|
106
|
+
|
107
|
+
def local_method?(name)
|
108
|
+
@local_methods.key?(name.to_sym)
|
80
109
|
end
|
81
110
|
|
82
111
|
def root?
|
@@ -103,14 +132,14 @@ class Environment
|
|
103
132
|
|
104
133
|
def add_local_method(name, &block)
|
105
134
|
raise ArgumentError.new "no method body provided" unless block_given?
|
106
|
-
@local_methods[name] = block # if name already exists, its function is overriden
|
107
|
-
name
|
135
|
+
@local_methods[name.to_sym] = block # if name already exists, its function is overriden
|
136
|
+
name.to_sym
|
108
137
|
end
|
109
138
|
|
110
139
|
# might not be useful
|
111
140
|
def clear_local_method(name)
|
112
|
-
@local_methods.delete(name)
|
113
|
-
name
|
141
|
+
@local_methods.delete(name.to_sym)
|
142
|
+
name.to_sym
|
114
143
|
end
|
115
144
|
|
116
145
|
def to_s
|
data/lib/rash/jobcontrol.rb
CHANGED
data/lib/rash/pipeline.rb
CHANGED
@@ -4,6 +4,10 @@ class Environment
|
|
4
4
|
@in_pipeline
|
5
5
|
end
|
6
6
|
|
7
|
+
def synced_pipeline?
|
8
|
+
@in_pipeline && @synchronous_pipeline
|
9
|
+
end
|
10
|
+
|
7
11
|
def make_pipeline(&block)
|
8
12
|
raise IOError.new("pipelining already enabled") if @in_pipeline
|
9
13
|
start_pipeline
|
@@ -14,9 +18,21 @@ class Environment
|
|
14
18
|
end
|
15
19
|
nil
|
16
20
|
end
|
21
|
+
|
22
|
+
def make_sync_pipeline(&block)
|
23
|
+
raise IOError.new("pipelining already enabled") if @in_pipeline
|
24
|
+
start_sync_pipeline
|
25
|
+
begin
|
26
|
+
block.call
|
27
|
+
ensure
|
28
|
+
end_sync_pipeline
|
29
|
+
end
|
30
|
+
nil
|
31
|
+
end
|
17
32
|
|
18
33
|
def as_pipe_command(&block)
|
19
34
|
raise IOError.new("pipelining not enabled") unless @in_pipeline
|
35
|
+
return as_sync_pipe_command(&block) if @synchronous_pipeline
|
20
36
|
|
21
37
|
input = (@active_pipelines.empty? ? $stdin : @active_pipelines.last.reader)
|
22
38
|
@active_pipelines << Pipeline.new
|
@@ -38,28 +54,81 @@ class Environment
|
|
38
54
|
nil
|
39
55
|
end
|
40
56
|
|
57
|
+
def as_sync_pipe_command(&block)
|
58
|
+
raise IOError.new("pipelining not enabled") unless @in_pipeline
|
59
|
+
raise IOError.new("pipeline is not synchronous") unless @synchronous_pipeline
|
60
|
+
|
61
|
+
@next_pipe.close
|
62
|
+
@next_pipe = Pipeline.new # flush the output pipe
|
63
|
+
@prev_pipe.writer.close
|
64
|
+
|
65
|
+
input = (@first_sync_command ? $stdin : @prev_pipe.reader)
|
66
|
+
@first_sync_command = false
|
67
|
+
output = @next_pipe.writer
|
68
|
+
error = ($stderr == $stdout ? @next_pipe.writer : $stdin)
|
69
|
+
|
70
|
+
pid = fork do
|
71
|
+
@in_pipeline = false
|
72
|
+
@synchronous_pipeline = false
|
73
|
+
$stdin = input
|
74
|
+
$stdout = output
|
75
|
+
$stderr = error
|
76
|
+
block.call
|
77
|
+
exit!(true)
|
78
|
+
end
|
79
|
+
|
80
|
+
Process.wait(pid)
|
81
|
+
@prev_pipe, @next_pipe = @next_pipe, @prev_pipe
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
41
85
|
private
|
42
86
|
|
43
87
|
def start_pipeline
|
44
88
|
@in_pipeline = true
|
45
89
|
end
|
46
90
|
|
91
|
+
def start_sync_pipeline
|
92
|
+
@in_pipeline = true
|
93
|
+
@synchronous_pipeline = true
|
94
|
+
@first_sync_command = true
|
95
|
+
@prev_pipe = Pipeline.new
|
96
|
+
@next_pipe = Pipeline.new
|
97
|
+
end
|
98
|
+
|
47
99
|
def end_pipeline
|
48
100
|
raise IOError.new("pipelining not enabled") unless @in_pipeline
|
49
101
|
@in_pipeline = false
|
50
102
|
if @active_pipelines.size > 0
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
103
|
+
begin
|
104
|
+
Process.wait(@active_pipelines.last.pid)
|
105
|
+
@active_pipelines.last.writer.close # probably redundant, but leaving it for now
|
106
|
+
IO.copy_stream(@active_pipelines.last.reader, $stdout)
|
107
|
+
@active_pipelines.pop.close
|
108
|
+
@active_pipelines.reverse_each {|pipe| pipe.terminate}
|
109
|
+
ensure
|
110
|
+
@active_pipelines.clear
|
111
|
+
end
|
57
112
|
end
|
58
113
|
end
|
59
114
|
|
115
|
+
def end_sync_pipeline
|
116
|
+
raise IOError.new("pipelining not enabled") unless @in_pipeline
|
117
|
+
raise IOError.new("pipeline is not synchronous") unless @synchronous_pipeline
|
118
|
+
@next_pipe.close
|
119
|
+
@prev_pipe.writer.close
|
120
|
+
IO.copy_stream(@prev_pipe.reader, $stdout)
|
121
|
+
@prev_pipe.close
|
122
|
+
|
123
|
+
@next_pipe = @prev_pipe = @first_sync_command = nil
|
124
|
+
@synchronous_pipeline = @in_pipeline = false
|
125
|
+
end
|
126
|
+
|
60
127
|
# special method to be referenced from Environment#dispatch. Do not use directly
|
61
128
|
def add_pipeline(m, *args)
|
62
129
|
raise IOError.new("pipelining not enabled") unless @in_pipeline
|
130
|
+
return add_sync_pipeline(m, *args) if @synchronous_pipeline
|
131
|
+
|
63
132
|
input = (@active_pipelines.empty? ? $stdin : @active_pipelines.last.reader)
|
64
133
|
@active_pipelines << Pipeline.new
|
65
134
|
output = @active_pipelines.last.writer
|
@@ -73,6 +142,23 @@ class Environment
|
|
73
142
|
@active_pipelines.last.link_process(pid)
|
74
143
|
end
|
75
144
|
|
145
|
+
def add_sync_pipeline(m, *args)
|
146
|
+
raise IOError.new("pipelining not enabled") unless @in_pipeline
|
147
|
+
raise IOError.new("pipeline is not synchronous") unless @synchronous_pipeline
|
148
|
+
|
149
|
+
# Ensure pipe is empty for writing
|
150
|
+
@next_pipe.close
|
151
|
+
@next_pipe = Pipeline.new
|
152
|
+
@prev_pipe.writer.close
|
153
|
+
|
154
|
+
input = (@first_sync_command ? $stdin : @prev_pipe.reader)
|
155
|
+
@first_sync_command = false
|
156
|
+
error = ($stderr == $stdout ? @next_pipe.writer : $stdin)
|
157
|
+
system_command(m, *args, out: @next_pipe.writer, input: input, err: error, except: true)
|
158
|
+
@prev_pipe, @next_pipe = @next_pipe, @prev_pipe
|
159
|
+
nil
|
160
|
+
end
|
161
|
+
|
76
162
|
class Pipeline
|
77
163
|
attr_reader :writer, :reader, :pid
|
78
164
|
|
@@ -103,6 +189,10 @@ class Environment
|
|
103
189
|
end
|
104
190
|
|
105
191
|
|
106
|
-
def in_pipeline(&block)
|
107
|
-
|
192
|
+
def in_pipeline(async: true, &block)
|
193
|
+
if async
|
194
|
+
$env.make_pipeline(&block)
|
195
|
+
else
|
196
|
+
$env.make_sync_pipeline(&block)
|
197
|
+
end
|
108
198
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rash-command-shell
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kellen Watt
|
@@ -24,7 +24,7 @@ dependencies:
|
|
24
24
|
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '1.2'
|
27
|
-
description: A Ruby-based shell
|
27
|
+
description: A Ruby-based command shell
|
28
28
|
email:
|
29
29
|
executables:
|
30
30
|
- rash
|