rash-command-shell 0.3.1 → 0.4.2.2

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: dfc5e5ad65a440c46d61615f3af54351e8cea6d65ca029c0a6d6a8ad8532d1ea
4
+ data.tar.gz: fe22cba6e10c9e03ff2dac578fb21f5247defb3e8523ae596f67b372667d1c2e
5
5
  SHA512:
6
- metadata.gz: 6e587842b8b49be6832f6c0f05a4ed5a48605712a2cff5b98684c00061292f4d332dba8e63dda380ffff2775535e33c78060649e0d34e1c2c9904b0b3665dad9
7
- data.tar.gz: 7b3ff24eb70080a2f70d1e39bcddb07b0d675465ff70e00e90e9e140443bec1c78c9bea22c47d52c9bd9685e01b4a9808736f373c6669bc9f7bb0743b11f635b
6
+ metadata.gz: 48d51cb3c68949accf870d3e80fd526d570d420fd28bc3de4a4a5a94ac18a3fb070c37531ef736a467e8c0642aa790f9ae99db9e565bbd46a12f0f170e86e5c1
7
+ data.tar.gz: c61f1b1689ccd5031b14c4a4bb14dba0b002f9d820af0eb0c4b1be03e13bb026449bfa0b5159d9aaa92c8e178f80e83cb7447ce17af6c64613295aa6838fa561
data/bin/rash CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  if ARGV.empty?
4
- exec("irb", "-r", "rash", *ARGV)
4
+ exec(["irb", "rash"], "-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.2.2" # 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
@@ -12,9 +12,24 @@ class Environment
12
12
  Dir.chdir(dir.nil? ? "~" : dir.to_s)
13
13
  @working_directory = Dir.pwd
14
14
  ENV["OLDPWD"] = old.to_s
15
+ ENV["PWD"] = Dir.pwd
15
16
  Dir.pwd
16
17
  end
17
-
18
+
19
+ # Note that this works regardless of which version of chdir is used.
20
+ def push_dir(dir = nil)
21
+ @directory_stack.push(Dir.pwd)
22
+ self.chdir(dir)
23
+ end
24
+
25
+ def pop_dir
26
+ self.chdir(@directory_stack.pop) if @directory_stack.size > 0
27
+ end
28
+
29
+ def dirs
30
+ @directory_stack
31
+ end
32
+
18
33
  def add_path(path)
19
34
  ENV["PATH"] += File::PATH_SEPARATOR + (path.respond_to?(:path) ? path.path : path.to_s)
20
35
  end
@@ -64,6 +79,10 @@ class Environment
64
79
  end
65
80
  end
66
81
 
82
+ def name?(v)
83
+ v.kind_of?(String) || v.kind_of?(Symbol)
84
+ end
85
+
67
86
  private
68
87
 
69
88
  def common_init
@@ -76,11 +95,12 @@ class Environment
76
95
 
77
96
  @active_pipelines = []
78
97
 
98
+ @directory_stack = []
99
+
79
100
  @prompt = {
80
101
  AUTO_INDENT: true,
81
102
  RETURN: ""
82
103
  }
83
- ENV["RASHDIR"] = File.dirname(__FILE__)
84
104
  end
85
105
 
86
106
  def resolve_command(m, *args, literal: false)
@@ -121,6 +141,17 @@ def cd(dir = nil)
121
141
  $env.chdir(d)
122
142
  end
123
143
 
144
+ def pushd(dir = nil)
145
+ case dir
146
+ when File, Dir
147
+ dir = dir.path if File.directory(dir.path)
148
+ end
149
+ $env.push_dir(dir)
150
+ end
151
+
152
+ def popd
153
+ $env.pop_dir
154
+ end
124
155
 
125
156
  def run(file, *args)
126
157
  filename = file.to_s
@@ -165,6 +196,11 @@ def which(command)
165
196
  nil
166
197
  end
167
198
 
199
+ # This breaks some default IRB functionality
200
+ # def self.respond_to_missing?(m, *args)
201
+ #
202
+ # which(m.to_s) || ($env.alias?(m) && !$env.aliasing_disabled) || $env.local_method?(m) || super
203
+ # end
168
204
 
169
205
  # Note that I defy convention and don't define `respond_to_missing?`. This
170
206
  # is because doing so screws with irb.
@@ -177,6 +213,6 @@ def self.method_missing(m, *args, &block)
177
213
  end
178
214
  end
179
215
 
180
-
216
+ Process.setproctitle("rash")
181
217
  run_command_file = "#{$env.HOME}/.rashrc"
182
218
  load run_command_file if File.file?(run_command_file)
@@ -6,30 +6,58 @@ class Environment
6
6
  def initialize
7
7
  common_init
8
8
  @working_directory = Directory.root("/")
9
- traverse_filetree("/", Dir.pwd)
10
9
  end
11
10
 
12
11
  def chdir(dir = nil)
13
12
  old = @working_directory
14
- traverse_filetree(Dir.pwd, (dir.nil? ? "~" : dir.to_s))
13
+ traverse_filetree(@working_directory.to_s, (dir.nil? ? "~" : dir.to_s))
15
14
  ENV["OLDPWD"] = old.to_s
15
+ ENV["PWD"] = Dir.pwd
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)
29
31
  end
30
32
 
33
+ def local_methods
34
+ @working_directory.local_methods
35
+ end
36
+
37
+ # This ultimately behaves similarly to a lambda in function if that ever changes,
38
+ # then the proc needs to be converted to a lambda.
31
39
  def local_call(name, *args, &block)
32
- @working_directory.local_methods[name.to_sym].call(*args, &block)
40
+ @working_directory.local_method(name).call(*args, &block)
41
+ end
42
+
43
+ def local_var(name, v = nil, locked: false)
44
+ res = nil
45
+ if v.nil?
46
+ res = @working_directory.local_variable(name)
47
+ else
48
+ @working_directory.set_local_variable(name, v)
49
+ res = v
50
+ end
51
+ @working_directory.lock_variable(name) if locked
52
+ res
53
+ end
54
+
55
+ def local_var?(name)
56
+ @working_directory.local_variable?(name)
57
+ end
58
+
59
+ def local_vars
60
+ @working_directory.local_variables
33
61
  end
34
62
 
35
63
  private
@@ -65,7 +93,6 @@ class Environment
65
93
  end
66
94
 
67
95
  class Directory
68
- attr_reader :local_methods
69
96
  attr_reader :parent, :children
70
97
 
71
98
  def self.root(dir)
@@ -76,9 +103,121 @@ class Environment
76
103
  @path = Dir.new(dir)
77
104
  @parent = parent
78
105
  @children = []
79
- @local_methods = parent&.local_methods.dup || {}
106
+ @local_methods = {}
107
+ @locked_methods = []
108
+ @local_variables = {}
109
+ @locked_variables = []
110
+ end
111
+
112
+ ######################
113
+ # Local method methods
114
+ ######################
115
+
116
+ def local_method(name)
117
+ name = name.to_sym
118
+ @local_methods[name] || @parent&.unlocked_local_method(name)
119
+ end
120
+
121
+ def local_method?(name)
122
+ name = name.to_sym
123
+ @local_methods.key?(name) || !!@parent&.unlocked_local_method?(name)
124
+ end
125
+
126
+ def local_methods
127
+ @local_methods.keys + (@parent&.unlocked_local_methods).to_a
128
+ end
129
+
130
+ def add_local_method(name, &block)
131
+ raise ArgumentError.new "no method body provided" unless block_given?
132
+ @local_methods[name.to_sym] = block # if name already exists, its function is overriden
133
+ name.to_sym
134
+ end
135
+
136
+ def unlocked_local_method(name)
137
+ name = name.to_sym
138
+ @local_methods[name] || @parent&.unlocked_local_method(name)
139
+ end
140
+
141
+ def unlocked_local_method?(name)
142
+ name = name.to_sym
143
+ @local_methods.filter{|k, v| !@locked_methods.include?(k)}.key?(name) ||
144
+ !!@parent&.unlocked_local_method?(name)
145
+ end
146
+
147
+ def unlocked_local_methods
148
+ @local_methods.filter{|k, v| !@locked_methods.include?(k)}.keys + (@parent&.unlocked_local_methods).to_a
149
+ end
150
+
151
+ def lock_method(name)
152
+ n = name.to_sym
153
+ raise NameError.new("#{name} is not a local method", n) unless @local_methods.key?(n)
154
+ @locked_methods << n unless @locked_methods.include?(n)
155
+ n
156
+ end
157
+
158
+ # might not be useful
159
+ def clear_local_method(name)
160
+ @local_methods.delete(name.to_sym)
161
+ name.to_sym
162
+ end
163
+
164
+ ######################
165
+ # Local variable stuff
166
+ ######################
167
+
168
+ def local_variable(name)
169
+ name = name.to_sym
170
+ @local_variables[name] || @parent&.unlocked_local_variable(name)
171
+ end
172
+
173
+ def local_variable?(name)
174
+ @local_variables.include?(name.to_sym) || !!@parent&.unlocked_local_variable?(name.to_sym)
175
+ end
176
+
177
+ def local_variables
178
+ @local_variables.keys + (@parent&.unlocked_local_variables).to_a
179
+ end
180
+
181
+ def set_local_variable(name, value)
182
+ name = name.to_sym
183
+ if !@local_variables.key?(name) && @parent&.unlocked_local_variable?(name)
184
+ @parent&.set_local_variable(name, value)
185
+ else
186
+ @local_variables[name] = value
187
+ end
188
+ end
189
+
190
+ def unlocked_local_variable(name)
191
+ name = name.to_sym
192
+ @local_variables.filter{|k| !@locked_variables.include?(k)}[name] || @parent&.unlocked_local_variable(name)
193
+ end
194
+
195
+ def unlocked_local_variable?(name)
196
+ name = name.to_sym
197
+ @local_variables.filter{|k,_v| !@locked_variables.include?(k)}.key?(name) ||
198
+ !!@parent&.unlocked_local_variable?(name)
199
+ end
200
+
201
+ def unlocked_local_variables
202
+ @local_variables.keys.filter{|k| !@locked_variables.include?(k)} + (@parent&.unlocked_local_variables).to_a
203
+ end
204
+
205
+ def lock_variable(name)
206
+ n = name.to_sym
207
+ raise NameError.new("#{name} is not a local variable", n) unless @local_variables.key?(n)
208
+ @locked_variables << n unless @locked_variables.include?(n)
209
+ n
210
+ end
211
+
212
+ def clear_local_variable(name)
213
+ @local_variables.delete(name.to_sym)
214
+ name.to_sym
80
215
  end
81
216
 
217
+ ###########################
218
+ # Generic traversal methods
219
+ ###########################
220
+
82
221
  def root?
83
222
  parent.nil?
84
223
  end
@@ -101,18 +240,6 @@ class Environment
101
240
  dir
102
241
  end
103
242
 
104
- def add_local_method(name, &block)
105
- 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
108
- end
109
-
110
- # might not be useful
111
- def clear_local_method(name)
112
- @local_methods.delete(name)
113
- name
114
- end
115
-
116
243
  def to_s
117
244
  @path.path
118
245
  end
@@ -121,10 +248,9 @@ end
121
248
 
122
249
  # still could absolutely be more cleaned up, but it works
123
250
  def self.method_missing(m, *args, &block)
124
- exe = which(m.to_s)
125
251
  if $env.local_method?(m)
126
252
  $env.local_call(m, *args, &block)
127
- elsif exe || ($env.alias?(m) && !$env.aliasing_disabled)
253
+ elsif which(m.to_s) || ($env.alias?(m) && !$env.aliasing_disabled)
128
254
  $env.dispatch(m, *args)
129
255
  else
130
256
  super
@@ -132,3 +258,4 @@ def self.method_missing(m, *args, &block)
132
258
  end
133
259
 
134
260
  $env = Environment.new
261
+ $env.chdir(Dir.pwd)
@@ -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
@@ -67,7 +67,7 @@ class Environment
67
67
  end
68
68
 
69
69
  def reset_stdin
70
- $stdin.close unless standard_stream>($stdin)
70
+ $stdin.close unless standard_stream?($stdin)
71
71
  $stdin = DEFAULT_IO[:in]
72
72
  end
73
73
 
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.2.2
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
@@ -43,7 +43,9 @@ files:
43
43
  homepage: https://github.com/KellenWatt/rash
44
44
  licenses:
45
45
  - MIT
46
- metadata: {}
46
+ metadata:
47
+ documentation_uri: https://github.com/KellenWatt/rash/wiki
48
+ wiki_uri: https://github.com/KellenWatt/rash/wiki
47
49
  post_install_message:
48
50
  rdoc_options: []
49
51
  require_paths: