rash-command-shell 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 19cb922bc55b83424f375e375c2015dff74690720b4d611357646bfd11e04c44
4
+ data.tar.gz: 48ce02ad077cd0a89a7fc62987907cbfc0ca80997e398c6e9699ac4beacf4521
5
+ SHA512:
6
+ metadata.gz: 6e1d9cd6e7270bd206d79db56b9ca20e2bab9b976c7e76b835c3af078e9a16816714807b8c96eafdc5f403369404bce243c31105bd33869fabb5fbffba65adf7
7
+ data.tar.gz: 6e36ba48448f4aed666807150a8819855ccb2e8802093f56cfc6f357277cd24a6ff91c43de0889affd15fddd2d642b1601ffcd7c347949766a434c124b1d7b1b
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if ARGV.size == 0
4
+ exec("irb", "-r", "rash")
5
+ else
6
+ require "rash"
7
+ file = ARGV.shift
8
+ load file
9
+ end
@@ -0,0 +1,130 @@
1
+ class Environment
2
+
3
+ attr_reader :aliasing_disabled
4
+ attr_reader :superuser_mode
5
+
6
+ def initialize
7
+ common_init
8
+ end
9
+
10
+ def chdir(dir = nil)
11
+ old = @working_directory
12
+ Dir.chdir(dir.nil? ? "~" : dir.to_s)
13
+ @working_directory = Dir.pwd
14
+ ENV["OLDPWD"] = old.to_s
15
+ Dir.pwd
16
+ end
17
+
18
+ def add_path(path)
19
+ ENV["PATH"] += File::PATH_SEPARATOR + (path.respond_to?(:path) ? path.path : path.to_s)
20
+ end
21
+
22
+ def method_missing(m, *args, &block)
23
+ if args.length == 0 && !block_given?
24
+ ENV[m.to_s.upcase]
25
+ elsif m.to_s[-1] == "=" && args.length == 1 && !block_given?
26
+ ENV[m.to_s.upcase.delete_suffix("=")] = args[0].to_s
27
+ else
28
+ super
29
+ end
30
+ end
31
+
32
+ def as_superuser(&block)
33
+ @superuser_mode = true
34
+ begin
35
+ block.call
36
+ ensure
37
+ @superuser_mode = false
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def common_init
44
+ @working_directory = Dir.pwd
45
+
46
+ @aliases = {}
47
+ @aliasing_disabled = false
48
+ @active_jobs = []
49
+
50
+ @prompt = {
51
+ AUTO_INDENT: true,
52
+ RETURN: ""
53
+ }
54
+ ENV["RASHDIR"] = File.dirname(__FILE__)
55
+ end
56
+ end
57
+
58
+ require_relative "rash/redirection"
59
+ require_relative "rash/aliasing"
60
+ require_relative "rash/jobcontrol"
61
+
62
+ $env = Environment.new
63
+
64
+
65
+ # note for later documentation: any aliases of cd must be functions, not
66
+ # environmental aliases. Limitation of implementation.
67
+ def cd(dir = nil)
68
+ $env.chdir(dir)
69
+ end
70
+
71
+
72
+ def run(file, *args)
73
+ filename = file.to_s
74
+ exe = (filename.start_with?("/") ? filename : File.expand_path(filename.strip))
75
+ unless File.executable?(exe)
76
+ raise SystemCallError.new("No such executable file - #{exe}", Errno::ENOENT::Errno)
77
+ end
78
+ system(exe, *args.flatten.map{|a| a.to_s}, {out: $stdout, err: $stderr, in: $stdin})
79
+ end
80
+
81
+
82
+ # Defines `bash` psuedo-compatibility. Filesystem effects happen like normal
83
+ # and environmental variable changes are copied
84
+ def sourcesh(file)
85
+ bash_env = lambda do |cmd = nil|
86
+ tmpenv = `#{cmd + ';' if cmd} printenv`
87
+ tmpenv.split("\n").grep(/[a-zA-Z0-9_]+=.*/).map {|l| l.split("=")}
88
+ end
89
+ bash_source = lambda do |f|
90
+ Hash[bash_env.call("source #{File.realpath f}") - bash_env.()]
91
+ end
92
+
93
+ bash_source.call(file).each {|k,v| ENV[k] = v if k != "SHLVL" && k != "_"}
94
+ end
95
+
96
+
97
+ def which(command)
98
+ cmd = File.expand_path(command)
99
+ return cmd if File.executable?(cmd) && !File.directory?(cmd)
100
+
101
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
102
+ ENV['PATH'].split(File::PATH_SEPARATOR).each do |pt|
103
+ path = File.expand_path(pt)
104
+ exts.each do |ext|
105
+ exe = File.join(path, "#{command}#{ext}")
106
+ return exe if File.executable?(exe) && !File.directory?(exe)
107
+ end
108
+ end
109
+ nil
110
+ end
111
+
112
+
113
+ # Note that I defy convention and don't define `respond_to_missing?`. This
114
+ # is because doing so screws with irb.
115
+ def self.method_missing(m, *args, &block)
116
+ exe = which(m.to_s)
117
+ if exe || ($env.alias?(m) && !$env.aliasing_disabled)
118
+ if $env.superuser_mode
119
+ system("sudo", *$env.resolve_alias(m), *args.flatten.map{|a| a.to_s}, {out: $stdout, err: $stderr, in: $stdin})
120
+ else
121
+ system(*$env.resolve_alias(m), *args.flatten.map{|a| a.to_s}, {out: $stdout, err: $stderr, in: $stdin})
122
+ end
123
+ else
124
+ super
125
+ end
126
+ end
127
+
128
+
129
+ run_command_file = "#{$env.HOME}/.rashrc"
130
+ load run_command_file if File.file?(run_command_file)
@@ -0,0 +1,55 @@
1
+ class Environment
2
+ def make_alias(new_func, old_func)
3
+ @aliases[new_func.to_sym] = old_func.to_s.split(" ")
4
+ end
5
+
6
+ def clear_alias(func)
7
+ @aliases.delete(func.to_sym)
8
+ end
9
+
10
+ # recursive aliases not currently possible. In the works
11
+ def resolve_alias(f)
12
+ result = [f.to_s]
13
+ aliases = @aliases.dup
14
+ found_alias = true
15
+ while found_alias
16
+ found_alias = false
17
+ if aliases.has_key?(result[0].to_sym)
18
+ found_alias = true
19
+ match = result[0].to_sym
20
+ result[0] = aliases[match]
21
+ aliases.delete(match)
22
+ result.flatten!
23
+ end
24
+ end
25
+ result
26
+ end
27
+
28
+ def alias?(f)
29
+ @aliases.has_key?(f.to_sym)
30
+ end
31
+
32
+ def without_aliasing
33
+ old_aliasing = @aliasing_disabled
34
+ @aliasing_disabled = true
35
+ if block_given?
36
+ begin
37
+ yield
38
+ ensure
39
+ @aliasing_disabled = old_aliasing
40
+ end
41
+ end
42
+ end
43
+
44
+ def with_aliasing
45
+ old_aliasing = @aliasing_disabled
46
+ @aliasing_disabled = false
47
+ if block_given?
48
+ begin
49
+ yield
50
+ ensure
51
+ @aliasing_disabled = old_aliasing
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,138 @@
1
+ class Environment
2
+
3
+ RASH_LOCAL_FILE = ".rashrc.local"
4
+
5
+ def initialize
6
+ common_init
7
+ @working_directory = Directory.root("/")
8
+ traverse_filetree("/", Dir.pwd)
9
+ end
10
+
11
+ def chdir(dir = nil)
12
+ old = @working_directory
13
+ traverse_filetree(Dir.pwd, (dir.nil? ? "~" : dir.to_s))
14
+ ENV["OLDPWD"] = old.to_s
15
+ Dir.pwd
16
+ end
17
+
18
+ def local_def(name, &block)
19
+ @working_directory.add_local_method(name.to_sym, &block)
20
+ end
21
+
22
+ def local_undef(name)
23
+ @working_directory.clear_local_method(name.to_sym)
24
+ end
25
+
26
+ def local_method?(name)
27
+ @working_directory.local_methods.key?(name.to_sym)
28
+ end
29
+
30
+ def local_call(name, *args, &block)
31
+ @working_directory.local_methods[name.to_sym].call(*args, &block)
32
+ end
33
+
34
+ private
35
+
36
+ LOCAL_FUNCTIONS_ENABLED = true
37
+
38
+ # from and to are strings
39
+ def traverse_filetree(from, to)
40
+ abs_from = File.expand_path(from)
41
+ abs_to = File.expand_path(to)
42
+ raise SystemCallError(from, Errno::ENOENT::Errno) unless Dir.exists?(abs_from)
43
+ raise SystemCallError(to, Errno::ENOENT::Errno) unless Dir.exists?(abs_to)
44
+
45
+ from_parts = (abs_from == "/" ? [""] : abs_from.split(File::SEPARATOR))
46
+ to_parts = (abs_to == "/" ? [""] : abs_to.split(File::SEPARATOR))
47
+ common_path = from_parts.filter.with_index {|p, i| p == to_parts[i]}
48
+
49
+ from_parts = from_parts.drop(common_path.size)
50
+ to_parts = to_parts.drop(common_path.size)
51
+
52
+ from_parts.each do |p|
53
+ @working_directory.add_parent(File.expand_path("..")) if @working_directory.root?
54
+ @working_directory = @working_directory.parent
55
+ Dir.chdir(@working_directory.to_s)
56
+ end
57
+
58
+ to_parts.each do |p|
59
+ @working_directory = @working_directory.child(File.expand_path(p, @working_directory.to_s))
60
+ Dir.chdir(@working_directory.to_s)
61
+ # rashrc_local = @working_directory.to_s + File::SEPARATOR + RASH_LOCAL_FILE
62
+ load RASH_LOCAL_FILE if File.exists?(RASH_LOCAL_FILE) && !File.directory?(RASH_LOCAL_FILE)
63
+ end
64
+
65
+ Dir.pwd
66
+ end
67
+
68
+ class Directory
69
+ attr_reader :local_methods
70
+ attr_reader :parent, :children
71
+
72
+ def self.root(dir)
73
+ Directory.new(nil, dir)
74
+ end
75
+
76
+ def initialize(parent, dir)
77
+ @path = Dir.new(dir)
78
+ @parent = parent
79
+ @children = []
80
+ @local_methods = parent&.local_methods.dup || {}
81
+ end
82
+
83
+ def root?
84
+ parent.nil?
85
+ end
86
+
87
+ def add_parent(dir)
88
+ @parent = Directory.root(dir)
89
+ @parent.add_child(self.to_s)
90
+ @parent
91
+ end
92
+
93
+ def child(path)
94
+ ch = @children.find {|c| c.to_s == path}
95
+ ch = add_child(path) unless ch
96
+ ch
97
+ end
98
+
99
+ def add_child(path)
100
+ dir = Directory.new(self, path)
101
+ @children << dir
102
+ dir
103
+ end
104
+
105
+ def add_local_method(name, &block)
106
+ raise ArgumentError.new "no method body provided" unless block_given?
107
+ @local_methods[name] = block # if name already exists, its function is overriden
108
+ name
109
+ end
110
+
111
+ # might not be useful
112
+ def clear_local_method(name)
113
+ @local_methods.delete(name)
114
+ name
115
+ end
116
+
117
+ def to_s
118
+ @path.path
119
+ end
120
+ end
121
+ end
122
+
123
+ def self.method_missing(m, *args, &block)
124
+ exe = which(m.to_s)
125
+ if $env.local_method?(m)
126
+ $env.local_call(m, *args, &block)
127
+ elsif exe || ($env.alias?(m) && !$env.aliasing_disabled)
128
+ if $env.superuser_mode
129
+ system("sudo", *$env.resolve_alias(m), *args.flatten.map{|a| a.to_s}, {out: $stdout, err: $stderr, in: $stdin})
130
+ else
131
+ system(*$env.resolve_alias(m), *args.flatten.map{|a| a.to_s}, {out: $stdout, err: $stderr, in: $stdin})
132
+ end
133
+ else
134
+ super
135
+ end
136
+ end
137
+
138
+ $env = Environment.new
@@ -0,0 +1,21 @@
1
+ class Environment
2
+ def jobs
3
+ @active_jobs.keep_if {|pid| Process.kill(0, pid) rescue false}
4
+ end
5
+
6
+ def async(&block)
7
+ pid = fork {
8
+ block.call
9
+ exit!(true)
10
+ }
11
+ @active_jobs << pid
12
+ Process.detach(pid)
13
+ pid
14
+ end
15
+ end
16
+
17
+ def as_background(&block)
18
+ $env.async(&block)
19
+ end
20
+
21
+
@@ -0,0 +1,80 @@
1
+ class Environment
2
+ attr_reader :prompt
3
+ def standard_prompt=(prompt)
4
+ @prompt[:prompt_i] = case prompt
5
+ when Proc
6
+ prompt
7
+ else
8
+ err_msg = "expecting stringable type or method that resolves to string"
9
+ raise ArgumentError.new(err_msg) unless prompt.respond_to?(:to_s)
10
+ lambda {prompt.to_s}
11
+ end
12
+ @prompt[:PROMPT_I] = "".tap {|s| def s.dup; $env.prompt[:prompt_i].call; end}
13
+ end
14
+
15
+ def indent_prompt=(prompt)
16
+ @prompt[:prompt_n] = case prompt
17
+ when Proc
18
+ prompt
19
+ else
20
+ err_msg = "expecting stringable type or method that resolves to string"
21
+ raise ArgumentError.new(err_msg) unless prompt.respond_to?(:to_s)
22
+ lambda {prompt.to_s}
23
+ end
24
+
25
+ @prompt[:PROMPT_N] = "".tap {|s| def s.dup; $env.prompt[:prompt_n].call; end}
26
+ end
27
+
28
+ def string_prompt=(prompt)
29
+ @prompt[:prompt_s] = case prompt
30
+ when Proc
31
+ prompt
32
+ else
33
+ err_msg = "expecting stringable type or method that resolves to string"
34
+ raise ArgumentError.new(err_msg) unless prompt.respond_to?(:to_s)
35
+ lambda {prompt.to_s}
36
+ end
37
+
38
+ @prompt[:PROMPT_S] = "".tap {|s| def s.dup; $env.prompt[:prompt_s].call; end}
39
+ end
40
+
41
+ def continued_prompt=(prompt)
42
+ @prompt[:prompt_c] = case prompt
43
+ when Proc
44
+ prompt
45
+ else
46
+ err_msg = "expecting stringable type or method that resolves to string"
47
+ raise ArgumentError.new(err_msg) unless prompt.respond_to?(:to_s)
48
+ lambda {prompt.to_s}
49
+ end
50
+
51
+ @prompt[:PROMPT_C] = "".tap {|s| def s.dup; $env.prompt[:prompt_c].call; end}
52
+ end
53
+
54
+ # This method can only be run from .rashrc. Anywhere else and it will simply do nothing
55
+ def return_value_header=(prompt)
56
+ @prompt[:RETURN] = prompt
57
+ end
58
+
59
+ def use_irb_prompt
60
+ if $0 == "irb"
61
+ IRB.conf[:PROMPT][:RASH] = @prompt
62
+ IRB.conf[:PROMPT_MODE] = :RASH
63
+ end
64
+ end
65
+ end
66
+
67
+ if $0 == "irb"
68
+ IRB.conf[:PROMPT][:RASH] = {
69
+ :PROMPT_I => "rash $",
70
+ :PROMPT_N => "rash ",
71
+ :PROMPT_S => "rash%l>",
72
+ :PROMPT_C => "rash >",
73
+ :RETURN => "%s\n" # used to printf
74
+ }
75
+ IRB.conf[:PROMPT_MODE] = :RASH
76
+ IRB.conf[:SAVE_HISTORY] = 1000
77
+ IRB.conf[:AP_NAME] = "rash"
78
+ IRB.conf[:AUTO_INDENT] = true
79
+ end
80
+
@@ -0,0 +1,151 @@
1
+ class Environment
2
+
3
+ DEFAULT_IO = {in: STDIN, out: STDOUT, err: STDERR}
4
+
5
+ def reset_io
6
+ reset_stdout
7
+ reset_stderr
8
+ reset_stdin
9
+ end
10
+
11
+ def stdout=(file)
12
+ $stdout.flush
13
+ old_stdout = $stdout
14
+ case file
15
+ when String
16
+ $stdout = File.new(file, "w")
17
+ when :out
18
+ $stdout = STDOUT
19
+ when :err
20
+ $stdout = STDERR
21
+ else
22
+ raise ArgumentError.new("not an output stream - #{file}") unless file.is_a?(IO)
23
+ $stdout = file
24
+ end
25
+ old_stdout.close unless standard_stream?(old_stdout)
26
+ end
27
+
28
+ def reset_stdout
29
+ $stdout.flush
30
+ $stdout.close unless standard_stream?($stdout)
31
+ $stdout = DEFAULT_IO[:out]
32
+ end
33
+
34
+ def stderr=(file)
35
+ $stderr.flush
36
+ old_stderr = $stderr
37
+ case file
38
+ when String
39
+ $stderr = File.new(file, "w")
40
+ when :out
41
+ $stderr = STDOUT
42
+ when :err
43
+ $stderr = STDERR
44
+ else
45
+ raise ArgumentError.new("not an output stream - #{file}") unless file.is_a?(IO)
46
+ $stderr = file
47
+ end
48
+ old_stderr.close unless standard_stream?(old_stderr)
49
+ end
50
+
51
+ def reset_stderr
52
+ $stderr.flush
53
+ $stderr.close unless standard_stream?($stderr)
54
+ $stderr = DEFAULT_IO[:err]
55
+ end
56
+
57
+ def stdin=(file)
58
+ old_stdin = $stdin
59
+ case file
60
+ when String
61
+ $stdin = File.new(file, "r")
62
+ when :in
63
+ $stdin = STDIN
64
+ else
65
+ raise ArgumentError.new("not an input stream - #{file}") unless file.is_a?(IO)
66
+ $stdin = file
67
+ end
68
+ old_stdin.close unless standard_stream?(old_stdin)
69
+ end
70
+
71
+ def reset_stdin
72
+ $stdin.close unless standard_stream>($stdin)
73
+ $stdin = DEFAULT_IO[:in]
74
+ end
75
+
76
+ private
77
+
78
+ def standard_stream?(f)
79
+ DEFAULT_IO.values.include?(f)
80
+ end
81
+ end
82
+
83
+ # If you want to append, you need to get the file object yourself.
84
+ # Check if not flushing immediately is a concern. If so, set $stdout.sync for files
85
+ def with_stdout_as(file = STDOUT)
86
+ $env.stdout = file
87
+ if block_given?
88
+ begin
89
+ yield $stdout
90
+ ensure
91
+ $env.reset_stdout
92
+ end
93
+ end
94
+ end
95
+
96
+ def with_stderr_as(file = STDERR)
97
+ $env.stderr = file
98
+ if block_given?
99
+ begin
100
+ yield $stderr
101
+ ensure
102
+ $env.reset_stderr
103
+ end
104
+ end
105
+ end
106
+
107
+ def with_stdin_as(file = STDIN)
108
+ $env.stdin = file
109
+ if block_given?
110
+ begin
111
+ yield $stdin
112
+ ensure
113
+ $env.reset_stdin
114
+ end
115
+ end
116
+ end
117
+
118
+ def with_stdout_as_stderr
119
+ $env.stdout = $stderr
120
+ if block_given?
121
+ begin
122
+ yield $stdout
123
+ ensure
124
+ $env.reset_stdout
125
+ end
126
+ end
127
+ end
128
+
129
+ def with_stderr_as_stdout
130
+ $env.stderr = $stdout
131
+ if block_given?
132
+ begin
133
+ yield $stderr
134
+ ensure
135
+ $env.reset_stderr
136
+ end
137
+ end
138
+ end
139
+
140
+ def with_all_out_as(file)
141
+ $env.stdout = file
142
+ $env.stderr = $stdout
143
+ if block_given?
144
+ begin
145
+ yield $stdout
146
+ ensure
147
+ $env.reset_stdout
148
+ $env.reset_stderr
149
+ end
150
+ end
151
+ end
metadata ADDED
@@ -0,0 +1,70 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rash-command-shell
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Kellen Watt
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-08-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: irb
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.2'
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 1.2.0
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: '1.2'
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 1.2.0
33
+ description: A Ruby-based shell
34
+ email:
35
+ executables:
36
+ - rash
37
+ extensions: []
38
+ extra_rdoc_files: []
39
+ files:
40
+ - bin/rash
41
+ - lib/rash.rb
42
+ - lib/rash/aliasing.rb
43
+ - lib/rash/ext/filesystem.rb
44
+ - lib/rash/jobcontrol.rb
45
+ - lib/rash/prompt/irb.rb
46
+ - lib/rash/redirection.rb
47
+ homepage: https://github.com/KellenWatt/rash
48
+ licenses:
49
+ - MIT
50
+ metadata: {}
51
+ post_install_message:
52
+ rdoc_options: []
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - ">="
63
+ - !ruby/object:Gem::Version
64
+ version: '0'
65
+ requirements: []
66
+ rubygems_version: 3.1.2
67
+ signing_key:
68
+ specification_version: 4
69
+ summary: Rash Ain't SH
70
+ test_files: []