Perform proper shell word splitting and improve logging

This commit is contained in:
Sami Samhuri 2022-01-16 16:31:45 -08:00
parent 01b0983f79
commit 9faa33fbf2
No known key found for this signature in database
GPG key ID: 4B4195422742FC16
3 changed files with 39 additions and 32 deletions

View file

@ -2,7 +2,7 @@ class Shell
class Builtins class Builtins
attr_reader :logger attr_reader :logger
def initialize(logger) def initialize(logger = ShellLogger.instance)
@logger = logger @logger = logger
end end

View file

@ -3,6 +3,7 @@
require 'English' require 'English'
require 'open3' require 'open3'
require 'readline' require 'readline'
require 'shellwords'
require './builtins' require './builtins'
require './colours' require './colours'
@ -12,12 +13,12 @@ require './job'
class Shell class Shell
attr_reader :logger, :options attr_reader :logger, :options
def initialize(args) def initialize(args = ARGV)
@logger = ShellLogger.new @builtins = Builtins.new
@options = parse_options(args)
@jobs_by_pid = {} @jobs_by_pid = {}
@builtins = Builtins.new(@logger) @logger = ShellLogger.instance
logger.verbose "options: #{options.inspect}" @options = parse_options(args)
logger.verbose "Options: #{options.inspect}"
end end
def main def main
@ -26,13 +27,13 @@ class Shell
if options[:command] if options[:command]
logger.verbose "Executing command: #{options[:command]}" logger.verbose "Executing command: #{options[:command]}"
print_logs print_logs
exit exec_command(options[:command]) exit process_command(options[:command])
elsif $stdin.isatty elsif $stdin.isatty
add_to_history = true add_to_history = true
loop do loop do
print_logs print_logs
cmd = Readline.readline(prompt(Dir.pwd), add_to_history) line = Readline.readline(prompt(Dir.pwd), add_to_history)
process_command(cmd) process_command(line)
end end
end end
end end
@ -78,39 +79,35 @@ class Shell
message = "#{log.message}#{CLEAR}" message = "#{log.message}#{CLEAR}"
case log.level case log.level
when :verbose when :verbose
puts message if options[:verbose] warn message if options[:verbose]
when :warning
warn message
else else
puts message warn message
end end
end end
logger.clear logger.clear
end end
def process_command(cmd) def process_command(line)
logger.verbose "Processing command: #{cmd.inspect}" logger.verbose "Processing command: #{line.inspect}"
exit 0 if cmd.nil? # EOF, ctrl-d exit 0 if line.nil? # EOF, ctrl-d
return if cmd.empty? # no input, no-op return if line.empty? # no input, no-op
# TODO: proper word splitting, pass arrays to built-ins args = Shellwords.split(line)
args = cmd.split cmd = args.shift
argv0 = args.first logger.verbose "Words: #{cmd} #{args.inspect}"
logger.verbose "\"Words\": #{args.inspect}" if @builtins.builtin?(cmd)
if @builtins.builtin?(argv0) logger.verbose "Executing builtin #{cmd}"
logger.verbose "Executing builtin #{argv0}" @builtins.exec(cmd, args)
args.shift
@builtins.exec(argv0, args)
else else
logger.verbose "Shelling out for #{argv0}" logger.verbose "Shelling out for #{cmd}"
status = exec_command(cmd) status = exec_command(cmd, args)
print "#{RED}-#{status}-#{CLEAR} " unless status.zero? print "#{RED}-#{status}-#{CLEAR} " unless status.zero?
end end
end end
def exec_command(cmd) def exec_command(cmd, args)
# TODO: background execution using fork + exec, streaming output # TODO: background execution using fork + exec, streaming output
out, err, status = Open3.capture3(cmd) out, err, status = Open3.capture3(cmd + ' ' + args.join(' '))
puts out.chomp unless out.empty? puts out.chomp unless out.empty?
warn err.chomp unless err.empty? warn err.chomp unless err.empty?
status.exitstatus status.exitstatus

View file

@ -1,3 +1,5 @@
require './colours'
# Queues up messages to be printed out when readline is waiting for input, to prevent # Queues up messages to be printed out when readline is waiting for input, to prevent
# mixing shell output with command output. # mixing shell output with command output.
class ShellLogger class ShellLogger
@ -5,21 +7,29 @@ class ShellLogger
attr_reader :logs attr_reader :logs
def self.instance
@instance ||= new
end
def initialize def initialize
clear clear
end end
def log(message) def log(message)
@logs << Log.new(:info, message) @logs << Log.new(:info, "#{WHITE}[INFO]#{CLEAR} #{message}")
end end
alias info log alias info log
def warn(message) def warn(message)
@logs << Log.new(:warning, message) @logs << Log.new(:warning, "#{YELLOW}[WARN]#{CLEAR} #{message}")
end
def error(message)
@logs << Log.new(:error, "#{RED}[ERROR]#{CLEAR} #{message}")
end end
def verbose(message) def verbose(message)
@logs << Log.new(:verbose, message) @logs << Log.new(:verbose, "[VERBOSE] #{message}")
end end
def clear def clear