mirror of
https://github.com/samsonjs/csc360-a1-shell.git
synced 2026-03-25 08:45:52 +00:00
Extract remaining code from main.rb
This commit is contained in:
parent
ec8baa2e1c
commit
9f0eb1378b
10 changed files with 163 additions and 125 deletions
10
ruby/a1
10
ruby/a1
|
|
@ -1,3 +1,9 @@
|
||||||
#!/bin/sh
|
#!/usr/bin/env ruby -w
|
||||||
|
|
||||||
./main.rb "$@"
|
require 'English'
|
||||||
|
|
||||||
|
$LOAD_PATH << File.expand_path(__dir__)
|
||||||
|
|
||||||
|
require 'shell'
|
||||||
|
|
||||||
|
Shell::CLI.new.run(args: ARGV) if $PROGRAM_NAME == __FILE__
|
||||||
|
|
|
||||||
108
ruby/main.rb
108
ruby/main.rb
|
|
@ -1,108 +0,0 @@
|
||||||
#!/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__
|
|
||||||
5
ruby/shell.rb
Normal file
5
ruby/shell.rb
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
require 'shell/cli'
|
||||||
|
require 'shell/repl'
|
||||||
|
|
||||||
|
module Shell
|
||||||
|
end
|
||||||
|
|
@ -1,9 +1,13 @@
|
||||||
class Shell
|
require 'shell/job_control'
|
||||||
class Builtins
|
require 'shell/logger'
|
||||||
attr_reader :logger
|
|
||||||
|
|
||||||
def initialize(job_control, logger = Logger.instance)
|
module Shell
|
||||||
@job_control = job_control
|
class Builtins
|
||||||
|
attr_reader :job_control, :logger
|
||||||
|
|
||||||
|
def initialize(job_control: nil, logger: nil)
|
||||||
|
logger ||= Logger.instance
|
||||||
|
@job_control = job_control || JobControl.new(logger: logger)
|
||||||
@logger = logger
|
@logger = logger
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -21,7 +25,7 @@ class Shell
|
||||||
|
|
||||||
def builtin_bg(args)
|
def builtin_bg(args)
|
||||||
cmd = args.shift
|
cmd = args.shift
|
||||||
@job_control.exec_command(cmd, args, background: true)
|
job_control.exec_command(cmd, args, background: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def builtin_cd(args)
|
def builtin_cd(args)
|
||||||
61
ruby/shell/cli.rb
Normal file
61
ruby/shell/cli.rb
Normal file
|
|
@ -0,0 +1,61 @@
|
||||||
|
require 'shell/colours'
|
||||||
|
require 'shell/logger'
|
||||||
|
require 'shell/repl'
|
||||||
|
|
||||||
|
module Shell
|
||||||
|
class CLI
|
||||||
|
include Colours
|
||||||
|
|
||||||
|
attr_reader :logger, :options, :repl
|
||||||
|
|
||||||
|
def initialize(logger: nil, repl: nil)
|
||||||
|
@logger = logger || Logger.instance
|
||||||
|
@options = {}
|
||||||
|
@repl = repl || REPL.new(logger: @logger)
|
||||||
|
@repl.precmd_hook = -> { print_logs }
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(args: nil)
|
||||||
|
@options = parse_options(args || ARGV)
|
||||||
|
logger.verbose "Options: #{options.inspect}"
|
||||||
|
if options[:command]
|
||||||
|
logger.verbose "Executing command: #{options[:command]}"
|
||||||
|
print_logs
|
||||||
|
exit repl.process_command(options[:command])
|
||||||
|
elsif $stdin.isatty
|
||||||
|
repl.start(options: options)
|
||||||
|
end
|
||||||
|
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
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
class Shell
|
module Shell
|
||||||
module Colours
|
module Colours
|
||||||
# These colours should be safe on dark and light backgrounds.
|
# These colours should be safe on dark and light backgrounds.
|
||||||
BLUE = "\033[1;34m".freeze
|
BLUE = "\033[1;34m".freeze
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
class Shell
|
module Shell
|
||||||
Job = Struct.new(:id, :pid, :cmd, :args)
|
Job = Struct.new(:id, :pid, :cmd, :args)
|
||||||
end
|
end
|
||||||
|
|
@ -1,15 +1,18 @@
|
||||||
require './colours'
|
require 'English'
|
||||||
require './job'
|
|
||||||
|
|
||||||
class Shell
|
require 'shell/colours'
|
||||||
|
require 'shell/job'
|
||||||
|
require 'shell/logger'
|
||||||
|
|
||||||
|
module Shell
|
||||||
class JobControl
|
class JobControl
|
||||||
include Colours
|
include Colours
|
||||||
|
|
||||||
attr_reader :logger
|
attr_reader :logger
|
||||||
|
|
||||||
def initialize(logger = Logger.instance)
|
def initialize(logger: nil)
|
||||||
@jobs_by_pid = {}
|
@jobs_by_pid = {}
|
||||||
@logger = logger
|
@logger = logger || Logger.instance
|
||||||
end
|
end
|
||||||
|
|
||||||
def trap_sigchld
|
def trap_sigchld
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
require './colours'
|
require 'shell/colours'
|
||||||
|
|
||||||
class Shell
|
module Shell
|
||||||
# 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 Logger
|
class Logger
|
||||||
67
ruby/shell/repl.rb
Normal file
67
ruby/shell/repl.rb
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
require 'readline'
|
||||||
|
require 'wordexp'
|
||||||
|
|
||||||
|
require 'shell/builtins'
|
||||||
|
require 'shell/colours'
|
||||||
|
require 'shell/job_control'
|
||||||
|
require 'shell/logger'
|
||||||
|
|
||||||
|
module Shell
|
||||||
|
class REPL
|
||||||
|
include Colours
|
||||||
|
|
||||||
|
attr_reader :builtins, :job_control, :logger, :options
|
||||||
|
|
||||||
|
attr_accessor :precmd_hook
|
||||||
|
|
||||||
|
def initialize(builtins: nil, job_control: nil, logger: nil)
|
||||||
|
logger ||= Logger.instance
|
||||||
|
job_control ||= JobControl.new(logger: logger)
|
||||||
|
builtins ||= Builtins.new(job_control: job_control)
|
||||||
|
|
||||||
|
@builtins = builtins
|
||||||
|
@job_control = job_control
|
||||||
|
@logger = logger
|
||||||
|
@options = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def start(options: nil)
|
||||||
|
@options = options || {}
|
||||||
|
job_control.trap_sigchld
|
||||||
|
add_to_history = true
|
||||||
|
status = 0
|
||||||
|
loop do
|
||||||
|
precmd_hook&.call
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
# Looks like this: /path/to/somewhere%
|
||||||
|
def prompt(pwd)
|
||||||
|
"#{blue(pwd)}#{white('%')} #{CLEAR}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue