-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathshell.py
executable file
·163 lines (145 loc) · 4.37 KB
/
shell.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
# Written for CS2600 Assignment 8 as myshell.py
import os
import sys
import platform
import signal
import readline
## BUILTINS
# Global variable holding a dictionary of builtin names
# and the functions they're associated with.
#
# { String => [List -> Boolean], ... }
#
BUILTINS = { }
## {BUILTIN} cd : List -> Boolean
# Attempts to change directories to the given path.
# Or prints a reasonable error message.
#
def cd(a):
home = os.getenv("HOME")
try:
if a: os.chdir(a[0].replace('~', home))
else: os.chdir(home)
return True
except Exception, e:
print(e.args[1])
return False
BUILTINS['cd'] = cd
## {BUILTIN} exit : List ->
# Exits from this shell :)
# We don't need to return here, because the program is done.
#
def exit(a):
sys.exit()
BUILTINS['exit'] = exit
## {BUILTIN} alias : List ->
# Delegates to add_alias with the right number of arguments.
#
def alias(a):
if len(a) == 0: print(ALIASES)
elif len(a) >= 2: add_alias(a[0], " ".join(a[1:]))
else: print("Wrong number of arguments for `alias`")
BUILTINS['alias'] = alias
## {BUILTIN} source : List ->
# Opens a script file and executes every line of it,
# then continues on prompting the user for input.
#
def source(a):
with open(a[0], 'r') as f:
for x in f:
execute(x.rstrip())
BUILTINS['source'] = source
## ALIASES
# Global variable holding a dictionary of aliases for commands.
# If a command matches an alias first it gets expanded then
# re-executed.
#
# { String => String, ... }
ALIASES = { }
## add_alias : String String -> void
# adds an alias to the given command with the given name.
#
def add_alias( name, command ):
ALIASES[name] = command
## is_interactive : -> Boolean
# Returns True when this shell is intended
# to be interacted with via terminal commands.
#
def is_interactive():
return len(sys.argv) == 1
## prompt : -> String
# Returns a string to be placed before user input.
#
def prompt():
pwd = os.getcwd().split('/').pop()
return pwd + " $ "
## get_command : File or False -> String
# Returns a string of the current command to be executed
#
# From STDIN:
# when shell is interactive commands come from STDIN.
# To make users lives better we'll place a prompt before
# the input region.
#
# From File:
# when shell is executing a script file, each command
# is read from the file, per line.
#
def get_command( script ):
if script:
command = script.readline()
if command == "": sys.exit() # EOF
return command
else: return raw_input(prompt())
## execute : String -> Boolean
# Executes the given system call or builtin, and returns
# true if execution was successful.
#
# NOTE: Builtins override system calls.
#
def execute( command ):
argv = command.split(" ")
if argv[0] in ALIASES:
# Substitute the aliased command
argv = ALIASES[argv[0]].split(" ") + argv[1:]
if argv[0] in BUILTINS:
# Always pass a list to this function, even if it's empty
return BUILTINS[argv.pop(0)](argv)
else:
# Handles comments and environment variables
return os.system(" ".join(argv)) == 0
# Trap SIGINT if is_interactive()
def sigint_handler( signal, frame ):
print '\nExit this shell with `exit`'
sys.stdout.write(prompt())
if is_interactive(): signal.signal(signal.SIGINT, sigint_handler)
# Open the given file if there was one
script = False if is_interactive() else open(sys.argv[1], 'r')
# Easy as that, tab completion making good use of the
# readline module.
def completer( text, state ):
incomplete = text.split('/').pop()
path = text.strip(incomplete)
r_path = '.' if path == "" else path
r_path = r_path.replace("~", os.getenv("HOME"))
matches = [f for f in os.listdir(r_path) if f.startswith(incomplete)]
if state < len(matches): return path + matches[state]
else: return None
# NOTE: In versions of python < 2.7 a trailing
# whitespace character will be added upon tab
# completion, this is a bug in python's readline
# module.
readline.set_completer(completer)
readline.set_completer_delims(' ')
if platform.system() == "Darwin":
readline.parse_and_bind("bind ^I rl_complete")
elif platform.system() == "Linux":
readline.parse_and_bind("tab: complete")
# Source our profile
try: source(["./profile.myshell"])
except IOError: print 'No profile.myshell file found in current directory'
# Execute every command
while True: execute(get_command(script))