From 9faa33fbf27950f95a06e581723ba81a7a53ee77 Mon Sep 17 00:00:00 2001 From: Sami Samhuri Date: Sun, 16 Jan 2022 16:31:45 -0800 Subject: [PATCH] Perform proper shell word splitting and improve logging --- ruby/builtins.rb | 2 +- ruby/main.rb | 53 +++++++++++++++++++++----------------------- ruby/shell_logger.rb | 16 ++++++++++--- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/ruby/builtins.rb b/ruby/builtins.rb index 56a0ded..b162315 100644 --- a/ruby/builtins.rb +++ b/ruby/builtins.rb @@ -2,7 +2,7 @@ class Shell class Builtins attr_reader :logger - def initialize(logger) + def initialize(logger = ShellLogger.instance) @logger = logger end diff --git a/ruby/main.rb b/ruby/main.rb index 568b0c7..32e8276 100755 --- a/ruby/main.rb +++ b/ruby/main.rb @@ -3,6 +3,7 @@ require 'English' require 'open3' require 'readline' +require 'shellwords' require './builtins' require './colours' @@ -12,12 +13,12 @@ require './job' class Shell attr_reader :logger, :options - def initialize(args) - @logger = ShellLogger.new - @options = parse_options(args) + def initialize(args = ARGV) + @builtins = Builtins.new @jobs_by_pid = {} - @builtins = Builtins.new(@logger) - logger.verbose "options: #{options.inspect}" + @logger = ShellLogger.instance + @options = parse_options(args) + logger.verbose "Options: #{options.inspect}" end def main @@ -26,13 +27,13 @@ class Shell if options[:command] logger.verbose "Executing command: #{options[:command]}" print_logs - exit exec_command(options[:command]) + exit process_command(options[:command]) elsif $stdin.isatty add_to_history = true loop do print_logs - cmd = Readline.readline(prompt(Dir.pwd), add_to_history) - process_command(cmd) + line = Readline.readline(prompt(Dir.pwd), add_to_history) + process_command(line) end end end @@ -78,39 +79,35 @@ class Shell message = "#{log.message}#{CLEAR}" case log.level when :verbose - puts message if options[:verbose] - when :warning - warn message + warn message if options[:verbose] else - puts message + warn message end end logger.clear end - def process_command(cmd) - logger.verbose "Processing command: #{cmd.inspect}" - exit 0 if cmd.nil? # EOF, ctrl-d - return if cmd.empty? # no input, no-op + def process_command(line) + logger.verbose "Processing command: #{line.inspect}" + exit 0 if line.nil? # EOF, ctrl-d + return if line.empty? # no input, no-op - # TODO: proper word splitting, pass arrays to built-ins - args = cmd.split - argv0 = args.first - logger.verbose "\"Words\": #{args.inspect}" - if @builtins.builtin?(argv0) - logger.verbose "Executing builtin #{argv0}" - args.shift - @builtins.exec(argv0, args) + args = Shellwords.split(line) + cmd = args.shift + logger.verbose "Words: #{cmd} #{args.inspect}" + if @builtins.builtin?(cmd) + logger.verbose "Executing builtin #{cmd}" + @builtins.exec(cmd, args) else - logger.verbose "Shelling out for #{argv0}" - status = exec_command(cmd) + logger.verbose "Shelling out for #{cmd}" + status = exec_command(cmd, args) print "#{RED}-#{status}-#{CLEAR} " unless status.zero? end end - def exec_command(cmd) + def exec_command(cmd, args) # 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? warn err.chomp unless err.empty? status.exitstatus diff --git a/ruby/shell_logger.rb b/ruby/shell_logger.rb index 73bd32c..060b040 100644 --- a/ruby/shell_logger.rb +++ b/ruby/shell_logger.rb @@ -1,3 +1,5 @@ +require './colours' + # Queues up messages to be printed out when readline is waiting for input, to prevent # mixing shell output with command output. class ShellLogger @@ -5,21 +7,29 @@ class ShellLogger attr_reader :logs + def self.instance + @instance ||= new + end + def initialize clear end def log(message) - @logs << Log.new(:info, message) + @logs << Log.new(:info, "#{WHITE}[INFO]#{CLEAR} #{message}") end alias info log 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 def verbose(message) - @logs << Log.new(:verbose, message) + @logs << Log.new(:verbose, "[VERBOSE] #{message}") end def clear