csc360-a1-shell/ruby/main.rb

108 lines
2.5 KiB
Ruby
Executable file

#!/usr/bin/env ruby -w
require 'English'
require 'readline'
require 'wordexp'
require './builtins'
require './colours'
require './job_control'
require './logger'
# TODO: change to module after extracting all or most of the code
class Shell
include Colours
attr_reader :builtins, :job_control, :logger, :options
def initialize(args: ARGV, builtins: nil, job_control: nil, logger: nil)
logger ||= Logger.instance
job_control ||= JobControl.new
builtins ||= Builtins.new(job_control)
@builtins = builtins
@job_control = job_control
@logger = logger
@options = parse_options(args)
logger.verbose "Options: #{options.inspect}"
end
def main
if options[:command]
logger.verbose "Executing command: #{options[:command]}"
print_logs
exit process_command(options[:command])
end
repl if $stdin.isatty
end
def repl
@job_control.trap_sigchld
add_to_history = true
status = 0
loop do
print_logs
print "#{red(status)} " unless status.zero?
line = Readline.readline(prompt(Dir.pwd), add_to_history)
Readline::HISTORY.pop if line.nil? || line.strip.empty?
status = process_command(line)
end
end
# Looks like this: /path/to/somewhere%
def prompt(pwd)
"#{blue(pwd)}#{white('%')} #{CLEAR}"
end
def parse_options(args)
options = {
verbose: false,
}
while (arg = args.shift)
case arg
when '-c'
options[:command] = args.shift
when '-v', '--verbose'
options[:verbose] = true
else
logger.warn "#{red('[ERROR]')} Unknown argument: #{arg}"
exit 1
end
end
options
end
def print_logs
logger.logs.each do |log|
message = "#{log.message}#{CLEAR}"
case log.level
when :verbose
warn message if options[:verbose]
else
warn message
end
end
logger.clear
end
def process_command(line)
exit 0 if line.nil? # EOF, ctrl-d
return 0 if line.strip.empty? # no input, no-op
logger.verbose "Processing command: #{line.inspect}"
args = Wordexp.expand(line)
cmd = args.shift
logger.verbose "Parsed command: #{cmd} #{args.inspect}"
if @builtins.builtin?(cmd)
logger.verbose "Executing builtin #{cmd}"
@builtins.exec(cmd, args)
else
logger.verbose "Shelling out for #{cmd}"
@job_control.exec_command(cmd, args)
end
rescue Errno => e
warn "#{red('[ERROR]')} #{e.message}"
-1
end
end
Shell.new(args: ARGV).main if $PROGRAM_NAME == __FILE__