#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # gameshell.py # # Copyright 2018 chees # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. # # Ver. 0.1.0029 import types as _types import traceback as _tb import math as _math _CONV24TO8 = 6 / 256 class Shell(object): def __init__(self): self.__commands = {'exit': self.exitShell} self.__aliases = {} self.ps1 = '> ' self.ps2 = '> ' self.colorMode = 0 # bits of color depth. Supports: 0, 3, 4, 8, 24 self.prevColor = '\x1b[0m' self.__exit = False def __color24(self, r, g, b, fg = True): if fg: return '\x1b[38;2;{0};{1};{2}m'.format(r, g, b) else: return '\x1b[48;2;{0};{1};{2}m'.format(r, g, b) def __color8(self, r, g, b, fg = True): r = _math.floor(r * _CONV24TO8) g = _math.floor(g * _CONV24TO8) b = _math.floor(b * _CONV24TO8) ret = 16 + b + 6 * g + 36 * r if fg: return '\x1b[38;5;{0}m'.format(ret) else: return '\x1b[48;5;{0}m'.format(ret) def __color4(self, r, g, b, fg = True): color = _math.floor(r / 128) + 2 * _math.floor(g / 128) + 4 * _math.floor(b / 128) r2, g2, b2 = r % 128, g % 128, b % 128 if r2 + g2 + b2 >= 192: color += 60 if fg: color += 30 else: color += 40 return '\x1b[{0}m'.format(color) def __color3(self, r, g, b, fg = True): color = _math.floor(r / 128) + 2 * _math.floor(g / 128) + 4 * _math.floor(b / 128) if fg: color += 30 else: color += 40 return '\x1b[{0}m'.format(color) def colorFromHex(self, color): """expects string formmatted like 3377DD""" return int(color[0:2], 16), int(color[2:4], 16), int(color[4:6], 16) def color(self, r, g = 0, b = 0, fg = True, setPrevColor = True): if isinstance(r, str): r, g, b = self.colorFromHex(r) ret = '' if self.colorMode == 0: # no color ret = '' elif self.colorMode == 3: ret = self.__color3(r, g, b, fg) elif self.colorMode == 4: ret = self.__color4(r, g, b, fg) elif self.colorMode == 8: ret = self.__color8(r, g, b, fg) elif self.colorMode == 24: ret = self.__color24(r, g, b, fg) else: ret = '' if ret == self.prevColor: return '' if setPrevColor: self.prevColor = ret return ret def setColor(self, r, g = 0, b = 0, fg = True): """Set the text color.""" print(color(r, g, b, fg), end = '') return def clearColor(self, setPrevColor = True): ret = '' if self.colorMode > 0: ret = '\x1b[0m' if ret == self.prevColor: return '' if setPrevColor: self.prevColor = ret return ret def setClearColor(self): print(clearColor()) return def run(self): """The main game/shell loop""" while not self.__exit: print(self.ps1, end = '') command = self.scanInput() # we have to handle shell built-ins first (when we get some) if len(command) == 0: continue if command[0] in self.__commands: try: self.__commands[command[0]](command[1:]) except Exception as e: _tb.print_exc() print(e) else: self.handleUnknownCommand(command) self.update() self.__exit = False def man(self, args): help(self.__commands[args[0]]) def registerCommand(self, commandName: str, command: _types.FunctionType): """command must be a function that takes one argument: a list of strings, conventionally called args or argv""" self.__commands[commandName] = command def registerAlias(self, a: str, original: list): """makes 'a' an alias for original. 'a' must be one token, but original can be multiple.""" self.__aliases[a] = original def getAlias(self, a: str): if a in self.__aliases: return self.__aliases[a] else: return None def exitShell(self, args): """The default exit command.""" self.__exit = True # Beyond this point are functions that are called within the main loop. def scanInput(self): """Parses input. Override this for custom input parsing, or input source.""" instr = input() if instr == '': return [] inQuotes = False ret = [] argStart = 0 c = 0 while c < len(instr): if inQuotes: if instr[c] == '"': inQuotes = False ret.append(instr[argStart:c]) argStart = c+1 else: if instr[c] == '"': inQuotes = True if argStart != c: ret.append(instr[argStart:c]) argStart = c+1 elif instr[c] in ' \t\n': if argStart != c: ret.append(instr[argStart:c]) argStart = c + 1 c += 1 if argStart != c: ret.append(instr[argStart:c]) a = self.getAlias(ret[0]) if a: ret = a[:] + ret[1:] return ret def handleUnknownCommand(self, command): """Handle commands that aren't registered. Override this if you want to do something with those commands.""" print("Bad command.") def update(self): """Runs at the end of each loop. Does nothing by default. Override this if there is something you want the shell to do between every command.""" pass def main(args): return 0 if __name__ == '__main__': import sys sys.exit(main(sys.argv))