Last active
December 10, 2015 23:59
-
-
Save sneakin/4513749 to your computer and use it in GitHub Desktop.
A very basic Unix shell using #method_missing.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
*~ | |
TODO.html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'pathname' | |
$: << Pathname.new(__FILE__).parent.parent.join("lib").to_s | |
require 'rash' | |
Rash.new do | |
$stderr.write("\r$ ") | |
begin | |
$stderr.puts("# => " + instance_eval($stdin.readline).to_s) | |
$stderr.write("\r$ ") | |
end until $stdin.eof? | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Create an instance to call shell commands as though they were instance methods. | |
class Rash | |
def initialize(&block) | |
instance_eval(&block) if block_given? | |
end | |
def exit(code = 0) | |
Kernel.exit(code) | |
end | |
# Executes the shell command sharing the missing method's name. Passes any arguments | |
# as strings and yields a read/write pipe if a block is given. | |
def method_missing(command, *arguments, &piper) | |
cmdline = ([command] + arguments).map(&:to_s) | |
if block_given? | |
IO.popen(cmdline, 'r+', &piper) | |
else | |
system(*cmdline) | |
end | |
$? && $?.exitstatus | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'pathname' | |
require 'open3' | |
describe 'bin/rash' do | |
context 'with no arguments' do | |
let(:cmd) { Pathname.new(__FILE__).parent.parent.parent.join("bin", "rash").to_s } | |
before do | |
@input, @output, @err, @wait_thr = Open3.popen3(cmd) | |
end | |
after do | |
@input.close unless @input.closed? | |
@output.close unless @output.closed? | |
@err.close unless @err.closed? | |
end | |
it "prints a prompt to stderr" do | |
@err.read(3).should == "\r$ " | |
end | |
it "reads and evaluates by line" do | |
@input.puts("echo 'hello'") | |
@output.readline.should == "hello\n" | |
@input.puts("echo 'world'") | |
@output.readline.should == "world\n" | |
end | |
it "prints the evaluated return value before the next prompt" do | |
@input.puts("ls '/boowho'") | |
@err.readline.should include('ls') | |
@err.readline.should == "# => 2\n" | |
@err.read(3).should == "\r\$ " | |
end | |
it "exits when the input stream closes" do | |
@wait_thr.should be_alive | |
@input.close | |
sleep(0.125) | |
@wait_thr.should_not be_alive | |
end | |
it "evaluates the input as Ruby" do | |
@input.puts("2 + 2") | |
@err.readline.should include("# => 4") | |
@input.puts("false") | |
@err.readline.should include("# => false") | |
@input.puts("self.false") | |
@err.readline.should include("# => 1") | |
@input.puts("x = 3; x + 2") | |
@err.readline.should include("# => 5") | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
describe Rash do | |
context 'initialization without arguments' do | |
it "does nothing" do | |
lambda { described_class.new }.should_not raise_error | |
end | |
end | |
context 'initialization with a block' do | |
it "evaluates the block" do | |
expect { |b| described_class.new(&b) }.to yield_control | |
end | |
end | |
describe '#method_missing' do | |
subject { described_class.new } | |
context 'without a block' do | |
it "executes the named shell command whose name matches the method's name" do | |
subject.should_receive(:system).with("ls") | |
subject.ls | |
end | |
it "passes the method's arguments to the command as strings" do | |
subject.should_receive(:system).with("echo", "hello", "123") | |
subject.echo(:hello, 123) | |
end | |
it "returns the shell command's exit status" do | |
subject.false.should == 1 | |
subject.true.should == 0 | |
end | |
end | |
context 'with a block' do | |
it "executes the named shell command whose name matches the method's name" do | |
subject.ls("/") do |io| | |
line = io.readline | |
%W(bin usr proc).should include(line.strip) | |
end | |
end | |
it "passes the method's arguments to the command as strings" do | |
subject.echo(:hello, 123 * 2) do |io| | |
io.readline.should == "hello 246\n" | |
end | |
end | |
it "yields a pipe for the command's stdin/out to the block" do | |
subject.sed('-e', 's/hello/goodbye/gi') do |io| | |
io.should be_kind_of(IO) | |
io.puts("Hello world") | |
io.close_write | |
io.readline.should == "goodbye world\n" | |
end | |
end | |
it "returns the shell command's exit status" do | |
subject.cat { |io| io.close_write }.should == 0 | |
subject.ls("wtf") { |io| io.close_write }.should_not == 0 | |
subject.false.should == 1 | |
subject.true.should == 0 | |
end | |
end | |
end | |
describe '#exit' do | |
it "exits Rash with the supplied exit code" do | |
Kernel.should_receive(:exit).with(123) | |
subject.exit(123) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
TODO | |
+ [ ] pipes: | |
#+BEGIN_SRC ruby | |
rash.echo("hello world") | rash.sed("-e", "s/hello/HELLO/") | rash.cat { |io| io.readline } # => "HELLO world\n" | |
#+END_SRC | |
+ [ ] redirection: ~< > >&~ | |
+ [ ] job control | |
+ [ ] exception handling | |
+ [ ] signal trapping | |
+ [ ] environent variables |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment