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 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