kopia lustrzana https://github.com/GuyCarver/MicroPython
366 wiersze
11 KiB
Python
366 wiersze
11 KiB
Python
![]() |
"""Implement a simple shell for running on MicroPython."""
|
||
|
|
||
|
# from __future__ import print_function
|
||
|
|
||
|
import os
|
||
|
import sys
|
||
|
import cmd
|
||
|
import pyb
|
||
|
import time
|
||
|
|
||
|
# TODO:
|
||
|
# - Need to figure out how to get input without echo for term_size
|
||
|
# - Add sys.stdin.isatty() for when we support reading from a file
|
||
|
# - Need to integrate readline in a python callable way (into cmd.py)
|
||
|
# so that the up-arrow works.
|
||
|
# - Need to define input command to use this under windows
|
||
|
|
||
|
MONTH = ('', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||
|
'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec')
|
||
|
|
||
|
|
||
|
def term_size():
|
||
|
"""Print out a sequence of ANSI escape code which will report back the
|
||
|
size of the window.
|
||
|
"""
|
||
|
# ESC 7 - Save cursor position
|
||
|
# ESC 8 - Restore cursor position
|
||
|
# ESC [r - Enable scrolling for entire display
|
||
|
# ESC [row;colH - Move to cursor position
|
||
|
# ESC [6n - Device Status Report - send ESC [row;colR
|
||
|
repl= None
|
||
|
if 'repl_source' in dir(pyb):
|
||
|
repl = pyb.repl_source()
|
||
|
if repl is None:
|
||
|
repl = pyb.USB_VCP()
|
||
|
repl.send(b'\x1b7\x1b[r\x1b[999;999H\x1b[6n')
|
||
|
pos = b''
|
||
|
while True:
|
||
|
char = repl.recv(1)
|
||
|
if char == b'R':
|
||
|
break
|
||
|
if char != b'\x1b' and char != b'[':
|
||
|
pos += char
|
||
|
repl.send(b'\x1b8')
|
||
|
(height, width) = [int(i, 10) for i in pos.split(b';')]
|
||
|
return height, width
|
||
|
|
||
|
# def term_size():
|
||
|
# return (25, 80)
|
||
|
|
||
|
|
||
|
def get_mode(filename):
|
||
|
try:
|
||
|
return os.stat(filename)[0]
|
||
|
except OSError:
|
||
|
return 0
|
||
|
|
||
|
|
||
|
def get_stat(filename):
|
||
|
try:
|
||
|
return os.stat(filename)
|
||
|
except OSError:
|
||
|
return (0, 0, 0, 0, 0, 0, 0, 0)
|
||
|
|
||
|
|
||
|
def mode_exists(mode):
|
||
|
return mode & 0xc000 != 0
|
||
|
|
||
|
|
||
|
def mode_isdir(mode):
|
||
|
return mode & 0x4000 != 0
|
||
|
|
||
|
|
||
|
def mode_isfile(mode):
|
||
|
return mode & 0x8000 != 0
|
||
|
|
||
|
|
||
|
def print_cols(words, termwidth=79):
|
||
|
"""Takes a single column of words, and prints it as multiple columns that
|
||
|
will fit in termwidth columns.
|
||
|
"""
|
||
|
width = max([len(word) for word in words])
|
||
|
nwords = len(words)
|
||
|
ncols = max(1, (termwidth + 1) // (width + 1))
|
||
|
nrows = (nwords + ncols - 1) // ncols
|
||
|
for row in range(nrows):
|
||
|
for i in range(row, nwords, nrows):
|
||
|
print('%-*s' % (width, words[i]),
|
||
|
end='\n' if i + nrows >= nwords else ' ')
|
||
|
|
||
|
|
||
|
def print_long(files):
|
||
|
"""Prints detailed information about each file passed in."""
|
||
|
for file in files:
|
||
|
stat = get_stat(file)
|
||
|
mode = stat[0]
|
||
|
if mode_isdir(mode):
|
||
|
mode_str = '/'
|
||
|
else:
|
||
|
mode_str = ''
|
||
|
size = stat[6]
|
||
|
mtime = stat[8]
|
||
|
localtime = time.localtime(mtime)
|
||
|
print('%6d %s %2d %02d:%02d %s%s' % (size, MONTH[localtime[1]],
|
||
|
localtime[2], localtime[4], localtime[5], file, mode_str))
|
||
|
|
||
|
|
||
|
def sdcard_present():
|
||
|
"""Determine if the sdcard is present. This current solution is specific
|
||
|
to the pyboard. We should really have a pyb.scard.detected() method
|
||
|
or something.
|
||
|
"""
|
||
|
return pyb.Pin.board.SD.value() == 0
|
||
|
|
||
|
|
||
|
class Shell(cmd.Cmd):
|
||
|
"""Implements the shell as a command line interpreter."""
|
||
|
|
||
|
def __init__(self, **kwargs):
|
||
|
(self.term_height, self.term_width) = term_size()
|
||
|
cmd.Cmd.__init__(self, **kwargs)
|
||
|
|
||
|
self.stdout_to_shell = self.stdout
|
||
|
|
||
|
self.cur_dir = os.getcwd()
|
||
|
self.set_prompt()
|
||
|
|
||
|
def set_prompt(self):
|
||
|
self.prompt = self.cur_dir + '> '
|
||
|
|
||
|
def resolve_path(self, path):
|
||
|
if path[0] != '/':
|
||
|
# Relative path
|
||
|
if self.cur_dir[-1] == '/':
|
||
|
path = self.cur_dir + path
|
||
|
else:
|
||
|
path = self.cur_dir + '/' + path
|
||
|
comps = path.split('/')
|
||
|
new_comps = []
|
||
|
for comp in comps:
|
||
|
if comp == '.':
|
||
|
continue
|
||
|
if comp == '..' and len(new_comps) > 1:
|
||
|
new_comps.pop()
|
||
|
else:
|
||
|
new_comps.append(comp)
|
||
|
if len(new_comps) == 1:
|
||
|
return new_comps[0] + '/'
|
||
|
return '/'.join(new_comps)
|
||
|
|
||
|
def emptyline(self):
|
||
|
"""We want empty lines to do nothing. By default they would repeat the
|
||
|
previous command.
|
||
|
|
||
|
"""
|
||
|
pass
|
||
|
|
||
|
def postcmd(self, stop, line):
|
||
|
self.stdout.close()
|
||
|
self.stdout = self.stdout_to_shell
|
||
|
self.set_prompt()
|
||
|
return stop
|
||
|
|
||
|
def line_to_args(self, line):
|
||
|
"""This will convert the line passed into the do_xxx functions into
|
||
|
an array of arguments and handle the Output Redirection Operator.
|
||
|
"""
|
||
|
args = line.split()
|
||
|
if '>' in args:
|
||
|
self.stdout = open(args[-1], 'a')
|
||
|
return args[:-2]
|
||
|
else:
|
||
|
return args
|
||
|
|
||
|
def help_args(self):
|
||
|
self.stdout.write('Prints out command line arguments.\n')
|
||
|
|
||
|
def do_args(self, line):
|
||
|
args = self.line_to_args(line)
|
||
|
for idx in range(len(args)):
|
||
|
print("arg[%d] = '%s'" % (idx, args[idx]))
|
||
|
|
||
|
def help_cat(self):
|
||
|
self.stdout.write('Concatinate files and send to stdout.\n')
|
||
|
|
||
|
def do_cat(self, line):
|
||
|
args = self.line_to_args(line)
|
||
|
for filename in args:
|
||
|
filename = self.resolve_path(filename)
|
||
|
mode = get_mode(filename)
|
||
|
if not mode_exists(mode):
|
||
|
self.stdout.write("Cannot access '%s': No such file\n" %
|
||
|
filename)
|
||
|
continue
|
||
|
if not mode_isfile(mode):
|
||
|
self.stdout.write("'%s': is not a file\n" % filename)
|
||
|
continue
|
||
|
with open(filename, 'r') as txtfile:
|
||
|
for line in txtfile:
|
||
|
self.stdout.write(line)
|
||
|
|
||
|
def help_cd(self):
|
||
|
self.stdout.write('Changes the current directory\n')
|
||
|
|
||
|
def do_cd(self, line):
|
||
|
args = self.line_to_args(line)
|
||
|
try:
|
||
|
dirname = self.resolve_path(args[0])
|
||
|
except IndexError:
|
||
|
dirname = '/'
|
||
|
mode = get_mode(dirname)
|
||
|
if mode_isdir(mode):
|
||
|
self.cur_dir = dirname
|
||
|
else:
|
||
|
self.stdout.write("Directory '%s' does not exist\n" % dirname)
|
||
|
|
||
|
def help_echo(self):
|
||
|
self.stdout.write('Display a line of text.\n')
|
||
|
|
||
|
def do_echo(self, line):
|
||
|
args = self.line_to_args(line)
|
||
|
self.stdout.write(args[0])
|
||
|
self.stdout.write('\n')
|
||
|
|
||
|
def help_help(self):
|
||
|
self.stdout.write('List available commands with "help" or detailed ' +
|
||
|
'help with "help cmd".\n')
|
||
|
|
||
|
def do_help(self, line):
|
||
|
cmd.Cmd.do_help(self, line)
|
||
|
|
||
|
def help_ls(self):
|
||
|
self.stdout.write('List directory contents.\n' +
|
||
|
'Use ls -a to show hidden files')
|
||
|
|
||
|
def do_ls(self, line):
|
||
|
args = self.line_to_args(line)
|
||
|
show_invisible = False
|
||
|
show_long = False
|
||
|
while len(args) > 0 and args[0][0] == '-':
|
||
|
if args[0] == '-a':
|
||
|
show_invisible = True
|
||
|
elif args[0] == '-l':
|
||
|
show_long = True
|
||
|
else:
|
||
|
self.stdout.write("Unrecognized option '%s'" % args[0])
|
||
|
return
|
||
|
args.remove(args[0])
|
||
|
if len(args) == 0:
|
||
|
args.append('.')
|
||
|
for idx in range(len(args)):
|
||
|
dirname = self.resolve_path(args[idx])
|
||
|
mode = get_mode(dirname)
|
||
|
if not mode_exists(mode):
|
||
|
self.stdout.write("Cannot access '%s': No such file or "
|
||
|
"directory\n" % dirname)
|
||
|
continue
|
||
|
if not mode_isdir(mode):
|
||
|
self.stdout.write(dirname)
|
||
|
self.stdout.write('\n')
|
||
|
continue
|
||
|
files = []
|
||
|
if len(args) > 1:
|
||
|
if idx > 0:
|
||
|
self.stdout.write('\n')
|
||
|
self.stdout.write("%s:\n" % dirname)
|
||
|
for filename in os.listdir(dirname):
|
||
|
if dirname[-1] == '/':
|
||
|
full_filename = dirname + filename
|
||
|
else:
|
||
|
full_filename = dirname + '/' + filename
|
||
|
|
||
|
mode = get_mode(full_filename)
|
||
|
if not show_long and mode_isdir(mode):
|
||
|
filename += '/'
|
||
|
if (show_invisible or
|
||
|
(filename[0] != '.' and filename[-1] != '~')):
|
||
|
files.append(filename)
|
||
|
if (len(files) > 0):
|
||
|
if show_long:
|
||
|
print_long(sorted(files))
|
||
|
else:
|
||
|
print_cols(sorted(files), self.term_width)
|
||
|
|
||
|
def help_micropython(self):
|
||
|
self.stdout.write('Micropython! Call any scripts! Interactive mode! ' +
|
||
|
'Quit with exit()')
|
||
|
|
||
|
def do_micropython(self, line):
|
||
|
args = self.line_to_args(line)
|
||
|
source = None
|
||
|
if len(args) == 1:
|
||
|
source = args[-1]
|
||
|
source = self.resolve_path(source)
|
||
|
mode = get_mode(source)
|
||
|
if not mode_exists(mode):
|
||
|
self.stdout.write("Cannot access '%s': No such file\n" %
|
||
|
source)
|
||
|
return
|
||
|
if not mode_isfile(mode):
|
||
|
self.stdout.write("'%s': is not a file\n" % source)
|
||
|
return
|
||
|
if source is None:
|
||
|
print('[Micropython]')
|
||
|
while True:
|
||
|
code_str = ''
|
||
|
line = input('|>>> ')
|
||
|
if line[0:4] == 'exit':
|
||
|
break
|
||
|
code_str += '%s\n' % line
|
||
|
if line[-1] == ':':
|
||
|
while True:
|
||
|
line = input('|... ')
|
||
|
if line == '':
|
||
|
break
|
||
|
code_str += '%s\n' % line
|
||
|
exec(code_str)
|
||
|
else:
|
||
|
code_str = ''
|
||
|
with open(source, 'r') as code:
|
||
|
for line in code:
|
||
|
code_str = code_str + line + '\n'
|
||
|
exec(code_str)
|
||
|
|
||
|
def help_mkdir(self):
|
||
|
self.stdout.write('Create directory.')
|
||
|
|
||
|
def do_mkdir(self, line):
|
||
|
args = self.line_to_args(line)
|
||
|
target = args[0]
|
||
|
mode = get_mode(target)
|
||
|
if not mode_exists(mode):
|
||
|
os.mkdir(target)
|
||
|
else:
|
||
|
print('%s already exists.' % target)
|
||
|
|
||
|
def help_rm(self):
|
||
|
self.stdout.write('Delete files and directories.')
|
||
|
|
||
|
def do_rm(self, line):
|
||
|
args = self.line_to_args(line)
|
||
|
if args[0] in ('pybcdc.inf', 'README.txt', 'boot.py', 'main.py'):
|
||
|
print('This file cannot be deleted')
|
||
|
try:
|
||
|
os.remove(args[0])
|
||
|
except:
|
||
|
try:
|
||
|
os.rmdir(args[0])
|
||
|
except:
|
||
|
print('%s is not a file or directory.' % args[0])
|
||
|
|
||
|
def help_EOF(self):
|
||
|
self.stdout.write('Control-D to quit.\n')
|
||
|
|
||
|
def do_EOF(self, _):
|
||
|
# The prompt will have been printed, so print a newline so that the
|
||
|
# REPL prompt shows up properly.
|
||
|
print('')
|
||
|
return True
|
||
|
|
||
|
|
||
|
def run():
|
||
|
Shell().cmdloop()
|
||
|
|
||
|
run()
|