rash-command-shell 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6654b1ecfaff25cb1008143b0c7718d5d36f6d7e3d3d3764ac77aff47c47e6ea
4
- data.tar.gz: 49436d272bf4a5bac674862c1541fe66f335751cf853482f027925b1e6d6f421
3
+ metadata.gz: 45f54bfa6b60f6a2a35a32d087ad1a722fb88f36fcd46933ae7145ee9dfc944d
4
+ data.tar.gz: 4dd605ed6fcbfb8a5fbacbd4ff11075933e3e6829785f7605442822f4e5fd508
5
5
  SHA512:
6
- metadata.gz: 6e587842b8b49be6832f6c0f05a4ed5a48605712a2cff5b98684c00061292f4d332dba8e63dda380ffff2775535e33c78060649e0d34e1c2c9904b0b3665dad9
7
- data.tar.gz: 7b3ff24eb70080a2f70d1e39bcddb07b0d675465ff70e00e90e9e140443bec1c78c9bea22c47d52c9bd9685e01b4a9808736f373c6669bc9f7bb0743b11f635b
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.2.2" # I may forget to update this
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
@@ -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
@@ -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.to_sym, &block)
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.to_sym)
26
+ @working_directory.clear_local_method(name)
25
27
  end
26
28
 
27
29
  def local_method?(name)
28
- @working_directory.local_methods.key?(name.to_sym)
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.local_methods[name.to_sym].call(*args, &block)
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&.local_methods.dup || {}
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
@@ -1,6 +1,6 @@
1
1
  class Environment
2
2
  def jobs
3
- @active_jobs.keep_if {|pid| Process.kill(0, pid) rescue false}
3
+ @active_jobs.keep_if{|pid| Process.kill(0, pid) rescue false}.dup
4
4
  end
5
5
 
6
6
  def async(&block)
@@ -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
- Process.wait(@active_pipelines.last.pid)
52
- @active_pipelines.last.writer.close # probably redundant, but leaving it for now
53
- IO.copy_stream(@active_pipelines.last.reader, $stdout)
54
- @active_pipelines.pop.close
55
- @active_pipelines.reverse_each {|pipe| pipe.terminate}
56
- @active_pipelines.clear
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
- $env.make_pipeline(&block)
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.3.1
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