diff --git a/builtin_apps/PyDOS/PyDOS.bmp b/builtin_apps/PyDOS/PyDOS.bmp new file mode 100644 index 0000000..6fe6f22 Binary files /dev/null and b/builtin_apps/PyDOS/PyDOS.bmp differ diff --git a/builtin_apps/PyDOS/code.py b/builtin_apps/PyDOS/code.py new file mode 100644 index 0000000..ca1bf7e --- /dev/null +++ b/builtin_apps/PyDOS/code.py @@ -0,0 +1,1273 @@ +import os +try: + from os import sep +except: + sep = os.getcwd()[0] +from time import localtime +from sys import stdin,implementation,path +try: + from traceback import print_exception,format_exception +except: + from sys import print_exception as sysprexcept + print_exception = lambda err,value=None,tb=None: sysprexcept(err) + format_exception = lambda err,value=None,tb=None: [err] +if not sep+'lib' in path: + path.insert(1,sep+'lib') +path.append(sep+'PyBasic') +try: + from pydos_ui import Pydos_ui +except ImportError: + Pydos_ui = None +try: + from pydos_ui import input +except ImportError: + pass + +import gc +imp = "B" +if implementation.name.upper() == "MICROPYTHON": + from micropython import mem_info + imp = "M" +elif implementation.name.upper() == "CIRCUITPYTHON": + if not Pydos_ui: + from supervisor import runtime, reload + imp = "C" + +gc.collect() +if 'threshold' in dir(gc): + gc.threshold(gc.mem_free() // 4 + gc.mem_alloc()) + +# The first string may contain wildcards +def _match(first, second): + + # If we reach end of both strings, we are done + if len(first) == 0 and len(second) == 0: + return True + + # Make sure that the characters after '*' are present + # in second string. Can't contain two consecutive '*' + if len(first) > 1 and first[0] == '*' and len(second) == 0: + return False + + if (len(first) > 1 and first[0] == '?') or (len(first) != 0 + and len(second) !=0 and first[0] == second[0]): + return _match(first[1:],second[1:]) + + if first[:1] == '*': + return _match(first[1:],second) or _match(first,second[1:]) + + return False + +def calcWildCardLen(wldCLen,recursiveFail): + wldCLen += 1 + if not recursiveFail and wldCLen < 115: + try: + (wldCLen,recursiveFail) = calcWildCardLen(wldCLen,recursiveFail) + except: + recursiveFail = True + + return (wldCLen,recursiveFail) + +def PyDOS(): + + global envVars + if "envVars" not in globals().keys(): + envVars = {} + _VER = "1.49" + prmpVals = ['>','(',')','&','|','\x1b','\b','<','=',' ',_VER,'\n','$',''] + + print("Starting Py-DOS...") + envVars["PATH"] = sep+";/PyBasic" + envVars["PROMPT"] = "$C$R$F$P$G" + envVars["LIB"] = ";".join(path[1:]) + envVars["DIRSEP"] = sep + if Pydos_ui: + (envVars["_scrHeight"],envVars["_scrWidth"]) = Pydos_ui.get_screensize() + else: + envVars["_scrHeight"] = 24 + envVars["_scrWidth"] = 80 + scrWdth = int(envVars["_scrWidth"]) + + wldCLen = 0 + recursiveFail = False + + (wldCLen,recursiveFail) = calcWildCardLen(wldCLen,recursiveFail) + wldCAdj = int(1+.2*wldCLen) + if imp == "C": + wldCAdj += 5 + wldCLen = max(1,wldCLen-wldCAdj) + + if wldCLen < 60: + print("Wild card length set to: ",wldCLen) + gc.collect() + + aFile = lambda dPth: bool(os.stat(dPth)[0]&(32768)) + + def anyKey(): + print("Press any key to continue . . . ."[:scrWdth],end="") + if Pydos_ui: + while not Pydos_ui.serial_bytes_available(): + pass + keyIn = Pydos_ui.read_keyboard(1) + else: + if imp == "C": + while not runtime.serial_bytes_available: + pass + keyIn = stdin.read(1) + print("") + return(keyIn) + + def scrnPause(swPause,nLines,scrnLine,scrnEnd=None): + quit = False + i = 0 + for sLine in scrnLine: + i += 1 + if swPause and nLines >= int(envVars["_scrHeight"])-1: + key = anyKey() + nLines = 0 + if key in "QqCc": + quit = True + break + if sLine is not None: + if scrnEnd is None or i -1: + exec(f'passedIn = "{passedIn}"\n{cf.read()}') + else: + exec(f"passedIn = '{passedIn}'\n{cf.read()}") + except Exception as err: + print_exception(err,err, \ + err.__traceback__ if hasattr(err,'__traceback__') else None) + envVars['lasterror'] = format_exception(err,err, \ + err.__traceback__ if hasattr(err,'__traceback__') else None) + except KeyboardInterrupt: + print("^C") + + return + + def chkPath(tstPath): + validPath = True + simpPath = "" + + if tstPath != []: + savDir = os.getcwd() + for path in tstPath: + if path[1:2] == ":": + path = path[2:] + if path == "": + os.chdir(sep) + elif os.getcwd() == sep and path == "..": + validPath = False + break + elif path == ".": + continue + elif path == ".." and len(os.getcwd().split(sep)) == 2: + os.chdir(sep) + elif path == "..": + os.chdir("..") + elif path in os.listdir() and not aFile(path): + os.chdir(path) + else: + validPath = False + break + + if validPath: + simpPath = os.getcwd() + os.chdir(savDir) + + return((validPath,simpPath)) + + def pFmt(dPath,trailsep=True): + if dPath == "": + return sep + elif dPath == sep: + return dPath + elif trailsep: + return dPath+(sep if dPath[-1]!=sep else "") + else: + return dPath[:(-1 if dPath[-1] == sep else None)] + + def absolutePath(argPath,currDir): + + if argPath[:1] == sep: + fullPath = argPath + elif currDir == sep: + fullPath = sep+argPath + else: + fullPath = currDir+sep+argPath + + fullPath = pFmt(fullPath,False) + + return(fullPath) + + srtFnc = lambda v,dP: f"{str(os.stat(dP+v)[0]&(32768))[0]}{v.lower()}*{v}" + + def dirLoop(tmpDir,lastDir,isFile,swPause,swWide,swRecur,prSum, \ + nLines=0,nFiles=0,tFSize=0,nDirs=0): + + wideCols = scrWdth//16 + wideCount = 0 + dirHeadPrntd = False + quit = False + + if "*" in lastDir or "?" in lastDir or isFile: + dirPat = lastDir + lastDir = "" + else: + dirPat = None + + dPath = pFmt(pFmt(tmpDir)+lastDir,False) + if dPath == sep+".": + dPath = sep + lastDir = "" + + if dirPat is None: + (quit,nLines) = scrnPause(swPause,nLines,["","Directory of "+dPath.replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/'))]) + dirHeadPrntd = True + nDirs += 2 + if swWide: + if wideCols > 1: + (quit,nLines) = scrnPause(swPause,nLines, \ + ["[.] [..] "],"") + wideCount += 2 + else: + (quit,nLines) = scrnPause(swPause,nLines,["[.]","[..]"],"") + wideCount = 1 + else: + scrAdj1 = 52 - min(scrWdth,52) + (quit,nLines) = scrnPause(swPause,nLines, \ + [f'.{" "*(23-scrAdj1)}',f'..{" "*(22-scrAdj1)}']) + + for _dir in sorted([srtFnc(x,pFmt(dPath)) for x in os.listdir(dPath)]): + _dir = _dir.split('*')[1] + + if (dirPat is None or _match(dirPat,_dir[:wldCLen])) and not quit: + dStat = os.stat(f"{pFmt(dPath)}{_dir}") + ForD = aFile(f"{pFmt(dPath)}{_dir}") + + if not dirHeadPrntd: + (quit,nLines) = scrnPause(swPause,nLines,["","Directory of "+dPath.replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/'))]) + if quit: + break + dirHeadPrntd = True + + if not ForD: + fSize = 0 + nDirs += 1 + else: + fSize = str(dStat[6]) + tFSize += int(fSize) + nFiles += 1 + + if swWide: + if wideCount >= wideCols: + wideCount = 0 + print() + nLines += 1 + wideCount += 1 + if not ForD: + (quit,nLines) = scrnPause(swPause,nLines, \ + [f'[{_dir[:13]}]{" "*(14-len(_dir[:13]))}'],"") + else: + (quit,nLines) = scrnPause(swPause,nLines, \ + [_dir[:15]+" "*(16-len(_dir[:15]))],"") + else: + + fTime = localtime(max(min(2145916800,dStat[9]),946684800)) + if not ForD: + scrAdj1 = 52 - min(scrWdth,52) + scrAdj2 = min(13,65-min(scrWdth,65)) + (quit,nLines) = scrnPause(swPause,nLines, \ + [f'{_dir[:max(8,scrWdth-26)]}{" "*(24-len(_dir)-scrAdj1)}{" "*(18-scrAdj2)}{fTime[1]:02}-{fTime[2]:02}-{fTime[0]:04} {fTime[3]:02}:{fTime[4]:02}']) + else: + scrAdj1 = 65 - min(scrWdth,65) + (quit,nLines) = scrnPause(swPause,nLines, \ + [f'{_dir[:max(8,scrWdth-20-len(fSize))]}{" "*(36-len(_dir)+10-len(fSize)-scrAdj1)}{fSize} {fTime[1]:02}-{fTime[2]:02}-{fTime[0]:04} {fTime[3]:02}:{fTime[4]:02}']) + + if quit: + break + + if not quit: + if swWide: + if dirHeadPrntd: + print() + nLines += 1 + + if swRecur: + for _dir in sorted(os.listdir(dPath), key=str.upper): + dStat = pFmt(dPath)+_dir + if not aFile(dStat): + try: + (nLines,nFiles,tFSize,nDirs,quit) = \ + dirLoop(dStat,(dirPat if dirPat is not None else ""), \ + isFile,swPause,swWide,swRecur,False, \ + nLines,nFiles,tFSize,nDirs) + except: + print("Recursion limit exceeded, Pystack too small") + quit = True + if quit: + break + + if prSum and not quit: + try: + availDisk = os.statvfs(dPath)[1]*os.statvfs(dPath)[4] + except: + availDisk = 0 + + scrAdj1 = 65 - min(scrWdth,65) + (quit,nLines) = scrnPause(swPause,nLines, \ + [(f'{" "*(4-len(str(nFiles)))} {nFiles} File(s){" "*(32-len(str(tFSize))-scrAdj1)} {tFSize} Bytes.')[:scrWdth], \ + (f'{" "*(4-len(str(nDirs)))} {nDirs} Dir(s){" "*(33-len(str(availDisk))-scrAdj1)} {availDisk} Bytes free.')[:scrWdth],""],"") + + return (nLines,nFiles,tFSize,nDirs,quit) + + def prDir(dirPath,swBits): + if swBits & (swAllB-int('010110',2)): + print("Illegal switch, Command Format: DIR[/p][/w][/s] [path][file]") + return + + savDir = os.getcwd() + fullPath = absolutePath(dirPath,savDir) + + pathDirs = fullPath.split(sep) + lastDir = pathDirs.pop(-1) + + (validPath, tmpDir) = chkPath(pathDirs) + + if validPath: + + os.chdir(tmpDir) + + if tmpDir == sep: + pathDirs = [""] + else: + pathDirs = tmpDir.split(sep) + + # Check for relative directory from possible mount point root + if len(pathDirs) == 2: + if lastDir == ".": + os.chdir(sep) + lastDir = tmpDir[1:] + elif lastDir == "..": + os.chdir(sep) + lastDir = "" + + tmpDir = os.getcwd() + + if lastDir in os.listdir() or lastDir in ".." or "*" in lastDir or "?" in lastDir or \ + swBits & int('000010',2): + + if lastDir in os.listdir(): + if aFile(pFmt(lastDir,False)): + isFile = True + else: + isFile = False + else: + if lastDir in ".." or (tmpDir == sep and lastDir == ""): + isFile = False + else: + isFile = True + + dirLoop(tmpDir,lastDir,isFile,bool(swBits&int('000100',2)),bool(swBits&int('010000',2)),bool(swBits&int('000010',2)),True) + else: + print("File",dirPath,"not found. (1)") + + os.chdir(savDir) + else: + print("File",dirPath,"not found. (2)") + + return + + def filecpy(file1,file2): + gc.collect() + with open(file2, "wb") as fCopy: + with open(file1, 'rb') as fOrig: + for line in fOrig: + fCopy.write(line) + return + + def delFiles(Dir,File,Recurs,removDirs): + for _dir in os.listdir(pFmt(Dir,False)): + if _match(File,_dir[:wldCLen]): + if File == "*" or File == "*.*" or _dir == _dir[:wldCLen]: + if aFile(Dir+_dir): + try: + os.remove(Dir+_dir) + print((Dir+_dir).replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/')),"deleted.") + except Exception as err: + print(f"Unable to delete: {Dir}{_dir}, Exception: {err}") + break + elif Recurs: + delFiles(Dir+_dir+sep,'*',Recurs,removDirs) + if removDirs: + if os.getcwd() == Dir+_dir: + os.chdir("..") + try: + os.rmdir(Dir+_dir) + except Exception as err: + print(f"Unable to remove: {Dir}{_dir}, Exception: {err}") + break + else: + print("Unable to delete: "+Dir+_dir+". Filename too long for wildcard operation.") + elif Recurs and not removDirs and not aFile(Dir+_dir): + delFiles(Dir+_dir+sep,File,Recurs,removDirs) + + def setCondCmd(args,i,condResult): + condCmd = "" + foundElse = False + + for _ in args[i:]: + + if condResult: + if _.upper() == "ELSE": + break + condCmd += (_+" ") + else: + if foundElse: + condCmd += (_+" ") + else: + if _.upper() == "ELSE": + foundElse = True + + return condCmd + + def readBATFile(BATfile): + batIndex = [0] + batLabels = {} + batLineNo = 0 + for batLine in BATfile: + batIndex.append(batIndex[batLineNo]+len(batLine)) + batLineNo += 1 + if batLine.strip()[:1] == ":" and len(batLine.strip().split(" ")[0]) > 1: + batLabels[batLine.strip().split(" ")[0][1:]] = [batLineNo,batIndex[batLineNo]] + BATfile.seek(0) + del batIndex,batLineNo + + return batLabels + + batEcho = True + cmd = "" + condCmd = "" + os.chdir(sep) + if "autoexec.bat" in ",".join(os.listdir()).lower().split(','): + activeBAT = True + BATfile = open(os.listdir()[(",".join(os.listdir()).lower().split(",")).index("autoexec.bat")]) + batLabels = readBATFile(BATfile) + batLineNo = 0 + batParams = [] + else: + activeBAT = False + gc.collect() + + while True: + scrWdth = int(envVars["_scrWidth"]) + if condCmd != "": + cmdLine = condCmd + condCmd = "" + else: + if activeBAT: + cmdLine = BATfile.readline() + i=1 + for param in batParams: + cmdLine = cmdLine.replace(f'%{i}',param) + i+=1 + if i>9: + break + + batLineNo += 1 + if cmdLine == "": + activeBAT = False + batEcho = True + BATfile.close() + gc.collect() + elif batEcho and cmdLine[0] !="@": + print(cmdLine,end="") + elif cmdLine[0] == "@": + cmdLine = cmdLine[1:] + + else: + prompt = "\n" + prmpLitrl = True + for prmpToken in envVars.get('PROMPT','$C$R$F$P$G').replace("$$","$."): + if prmpToken == '$': + prmpLitrl = False + continue + if prmpLitrl: + prompt += prmpToken + else: + prmpToken = prmpToken.upper() + prmpLitrl = True + if prmpToken == 'R': + if 'mem_free' in dir(gc): + prompt += str(gc.mem_free()) + elif prmpToken == 'D': + prompt += f"{localtime()[1]:02}/{localtime()[2]:02}/{localtime()[0]:04}" + elif prmpToken == 'T': + prompt += f"{localtime()[3]:02}:{localtime()[4]:02}:{localtime()[5]:02}" + elif prmpToken == 'P': + prompt += os.getcwd().replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/')) + else: + prompt += prmpVals['GCFABEHLQSV_.'.find(prmpToken)] + cmdLine = input(prompt) + + cmdLine = cmdLine.strip() + + envFound = False + fndVar = "" + newCmdLine = "" + for _ in cmdLine: + if not envFound: + if _ == "%": + envFound = True + doublepct = True + else: + newCmdLine += _ + else: + if _ == "%": + if doublepct: + newCmdLine += "%" + envFound = False + newCmdLine += str(envVars.get(fndVar,"")) + fndVar = "" + else: + doublepct = False + fndVar += _ + + if envVars.get('DIRSEP','/') == '\\': + switches = [] + switch = "" + nxt = False + cmdLine = newCmdLine[:1].replace('\\','/') + + for _ in newCmdLine[1:]: + if nxt and _ not in " /": + switch += _.upper() + elif _ == '/': + nxt = True + if switch != "": + switches.append(switch) + switch = "" + elif nxt and _ == " ": + cmdLine += _ + nxt = False + switches.append(switch) + switch = "" + elif _ == "\\": + cmdLine += sep + else: + cmdLine += _ + if switch != "": + switches.append(switch) + else: + cmdLine = newCmdLine + + args = cmdLine.split(" ") + + quotedArg = False + if len(args) > 1: + i = 0 + iEnd = len(args) + for _ in range(0, iEnd): + if args[i].strip() == "": + args.pop(i) + elif quotedArg: + if args[i].find('"') > -1 and args[i].find('"') != len(args[i])-1: + break + elif args[i][-1] == '"': + args[i-1] = f"{args[i-1]} {args.pop(i)[:-1]}" + quotedArg = False + else: + args[i-1] = f"{args[i-1]} {args.pop(i)}" + elif args[i][0] == '"': + if args[i][-1] != '"': + args[i] = args[i][1:] + i += 1 + quotedArg = True + else: + args[i] = args[i][1:-1] + i += 1 + else: + i += 1 + + if cmdLine != "" and cmdLine[0] != '/': + tmp = (args[0].upper()).split('/') + if envVars.get('DIRSEP','/') != '\\': + switches = tmp + cmd = tmp.pop(0) + else: + if envVars.get('DIRSEP','/') != '\\': + switches = [] + cmd = args[0] + + # Error=1, (S)Recur=2, (P)Pause=4, (Y)Conf=8, (W)Wide=16, (D)debug=32, (Q)uiet=64 + # (V)erify=128 + swBits = 0 + swAllB = int('11111111',2) + for i in range(len(switches)): + swBits = swBits | (2**('SPYWDQV'.find(switches[i])+1)) + + if quotedArg: + print("Mismatched quotes.") + cmd = "" + + if cmd in ["DELETE","DEL","TYPE","MORE","MKDIR","MD","RMDIR","RD","COPY", \ + "CHDIR","CD","RENAME","REN","MOVE","DELTREE"]: + if len(args) > 1: + savDir = os.getcwd() + args[1] = absolutePath(args[1],savDir) + aPath = args[1].split(sep) + if cmd not in ["RMDIR","RD","CHDIR","CD","DELTREE"]: + newdir = aPath.pop(-1) + (validPath,tmpDir) = chkPath(aPath) + if cmd in ["DELETE","DEL","TYPE","MORE","MKDIR","MD"]: + tmpDir = pFmt(tmpDir) + else: + tmpDir = pFmt(tmpDir,False) + + if cmd == "" or cmd == "REM": + continue + elif cmd == "DIR": + if len(args) == 1: + prDir(os.getcwd()[(2 if os.getcwd()[1:2]==":" else 0):],swBits) + elif len(args) == 2: + prDir(args[1],swBits) + else: + print("Too many arguments. Command Format: DIR/p/w/s [path][file]") + + elif cmd == "DATE": + i = localtime()[6]*3 + print(f'The current date is: {"MonTueWedThuFriSatSun"[i:i+3]} {localtime()[1]:02}/{localtime()[2]:02}/{localtime()[0]:04}') + + elif cmd == "TIME": + print(f'The current time is: {localtime()[3]%12}:{localtime()[4]:02}:{localtime()[5]:02} {["AM","PM"][localtime()[3]//12]}') + + elif cmd == "MEM": + gc.collect() + print(f"\n{gc.mem_free()/1024:10.1f} Kb free conventional memory") + print(f"{gc.mem_alloc()/1024:10.1f} Kb used conventional memory") + if imp == "M": + if 'threshold' in dir(gc): + print(f"{gc.threshold()/1024:10.1f} Kb current threshold value") + + if swBits & int('010000',2): + print(mem_info(1)) + elif swBits: + print("Illegal switch, Command Format: mem[/d]") + + elif cmd == "VER": + print(f"PyDOS [Version {_VER}]") + + elif cmd == "ECHO": + if len(args) == 1: + print("Echo is "+("on." if batEcho else "off.")) + else: + if args[1].upper() == 'ON': + batEcho = True + elif args[1].upper() == 'OFF': + batEcho = False + else: + print(cmdLine[5:].replace("\e",chr(27)).replace('\x1b',chr(27)).replace("\E",chr(27)).replace('\X1B',chr(27))) + + elif cmd == "PAUSE": + anyKey() + + elif cmd[0] == ":" and activeBAT: + if len(args[0]) <= 1 or len(args) != 1: + print("Invalid batch label") + condCmd = "exit" + + elif cmd == "GOTO" and activeBAT: + if len(args) == 2: + try: + BATfile.seek(batLabels[args[1]][1]) + except: + print("Invalid Goto label:",args[1]) + condCmd = "exit" + + batLineNo = batLabels.get(args[1],[batLineNo+1,0])[0] + else: + print("Invalid Goto label:",cmdLine) + condCmd = "exit" + + elif cmd == "IF" and activeBAT: + condResult = False + + if len(args) < 3: + print("Invalid command format:",cmdLine) + condCmd = "exit" + else: + i = 1 + notlogic = False + if args[1].upper() == "NOT": + notlogic = True + i = 2 + + if args[i].strip().upper() == 'ERRORLEVEL': + i += 1 + if len(args) > i and args[i].isdigit(): + if str(envVars.get('errorlevel')).isdigit() and int(envVars.get('errorlevel')) == int(args[i]): + condResult = True + + if notlogic: + condResult = not condResult + + i += 1 + condCmd = setCondCmd(args,i,condResult) + else: + print("Invalid conditional ERRORLEVEL:",cmdLine) + condCmd = "exit" + + elif args[i].strip().upper() == 'EXIST': + i += 1 + if len(args) > i: + savDir = os.getcwd() + args[i] = absolutePath(args[i],savDir) + + aPath = args[i].split(sep) + newdir = aPath.pop(-1) + (validPath, tmpDir) = chkPath(aPath) + tmpDir = pFmt(tmpDir) + + if validPath and newdir in os.listdir(pFmt(tmpDir,False)): + condResult = True + if notlogic: + condResult = not condResult + + i += 1 + condCmd = setCondCmd(args,i,condResult) + + else: + print("Invalid conditional EXIST:",cmdLine) + condCmd = "exit" + else: + # string comparison + if len(args) > i: + string1 = args[i] + if "==" in args[i]: + string1 = args[i].split("==")[0] + if args[i][-1] != "=": + string2 = args[i].split("==")[-1] + else: + i += 1 + if len(args) > i: + string2 = args[i] + else: + print("Invalid string conditional:",cmdLine) + condCmd = "exit" + elif len(args) > i+1: + i += 1 + if "==" in args[i]: + if len(args[i]) > 2: + string2 = args[i].split("==")[-1] + else: + i+=1 + if len(args) > i: + string2 = args[i] + else: + print("Invalid string conditional:",cmdLine) + condCmd = "exit" + else: + print("Invalid string conditional:",cmdLine) + condCmd = "exit" + else: + print("Invalid string conditional:",cmdLine) + condCmd = "exit" + + + if condCmd != "exit": + if string1 == string2: + condResult = True + if notlogic: + condResult = not condResult + + i += 1 + condCmd = setCondCmd(args,i,condResult) + else: + print("Invalid string conditional:",cmdLine) + condCmd = "exit" + + elif cmd == "SET": + if len(args) == 1: + for _ in sorted(envVars): + print(f"{_}={envVars[_]}") + else: + args = cmdLine.split(" ") + args.pop(0) + envCmd = (" ".join(args)).split("=") + envCmdVar = envCmd.pop(0).strip() + + if len(switches) <= 1: + if len(switches) == 0: + tmp = "=".join(envCmd).strip() + elif switches[0] == 'A': + # Replace all possible environment variables with their values + envCmd = "=".join(envCmd) + for _ in " %*()-+/": + envCmd = envCmd.replace(_," ") + envCmd = envCmd.split(" ") + + for _ in envCmd: + if _[:1].isalpha() or _[:1] == "_": + cmdLine = cmdLine.replace(_.strip(),str(envVars.get(_,0))) + + # Evaluate right sight of = after value substituion + args = cmdLine.split(" ") + args.pop(0) + envCmd = (" ".join(args)).split("=") + envCmdVar = envCmd.pop(0).strip() + try: + envVars[envCmdVar] = str(eval("=".join(envCmd).strip())) + except: + envVars[envCmdVar] = "0" + elif switches[0] == "P": + tmp = input("=".join(envCmd).strip()+" ") + else: + print("Illegal switch, Command Format: SET[/a|/p] [variable = [string|expression]]") + + if len(switches) == 0 or switches[0] == "P": + if tmp != "": + envVars[envCmdVar] = tmp + elif envCmdVar == "_scrHeight" or envCmdVar == "_scrWidth": + if Pydos_ui: + (tHeight,tWidth) = Pydos_ui.get_screensize(envVars.get('_display')) + else: + tHeight = 24 + tWidth = 80 + if envCmdVar == "_scrWidth": + envVars[envCmdVar] = tWidth + else: + envVars[envCmdVar] = tHeight + elif envVars.get(envCmdVar) != None: + envVars.pop(envCmdVar) + + scrWdth = int(envVars["_scrWidth"]) + if envCmdVar == 'LIB': + path.clear() + path.extend(['']+envVars.get("LIB","").split(';')) + else: + print("Illegal switch, Command Format: SET[/a|/p] [variable = [string|expression]]") + + elif cmd in ["PROMPT","PATH"]: + if len(args) == 1: + print(cmd+"="+envVars.get(cmd,"")) + else: + envVars[cmd] = args[1] + + elif cmd in ["RENAME","REN","MOVE"]: +# todo: allow source to be file and target directory? +# Wildcard renames +# renames across sd mount points + + if len(args) == 3: +# Check that first argument has a valid path and exists + if validPath and newdir in os.listdir(tmpDir) and args[1][-1] != sep: + + args[2] = absolutePath(args[2],savDir) + aPath2 = args[2].split(sep) + newdir2 = aPath2.pop(-1) + (validPath, tmpDir2) = chkPath(aPath2) + + if newdir2 == '*': + newdir2 = newdir +# Second argument has valid path + if validPath and args[2][-1] != sep and '?' not in newdir2 and \ + '*' not in newdir2 and newdir2 !="." and newdir2 != "..": + +# second argument doesn't specify an existing target + if newdir2 not in os.listdir(tmpDir2): + currDRen = False + if not aFile(tmpDir+sep+newdir): + if tmpDir+sep+newdir == os.getcwd(): + currDRen = True + os.rename(pFmt(tmpDir)+newdir,pFmt(tmpDir2)+newdir2) + if currDRen: + os.chdir(tmpDir2) + + else: + print("Target file exists") + else: + print("Invalid target:",args[2]) + else: + print("Invalid source:",args[1]) + else: + print("Wrong number of arguments") + + elif cmd in ["DELETE","DEL"]: + + if len(args) == 2: + if validPath: + if not (swAllB-int('000010',2)) & swBits: + if "*" in newdir or "?" in newdir: + ans = "Y" + if newdir == "*" or newdir == "*.*": + ans = input(tmpDir+newdir+", Are you sure (y/n)? ").upper() + + if ans == "Y": + delFiles(tmpDir,newdir,bool(swBits&int('000010',2)),False) + else: + if newdir in os.listdir(pFmt(tmpDir,False)): + if aFile(tmpDir+newdir): + os.remove(tmpDir+newdir) + print((tmpDir+newdir).replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/')),"deleted.") + else: + ans = input(tmpDir+newdir+sep+"*, Are you sure (y/n)? ").upper() + if ans == "Y": + delFiles(tmpDir+newdir+sep,'*',bool(swBits&int('000010',2)),False) + else: + if swBits & int('000010',2): + ans = input(tmpDir+newdir+sep+"*, Are you sure (y/n)? ").upper() + if ans == "Y": + delFiles(tmpDir,newdir,True,False) + else: + print(f"Unable to delete: {tmpDir}{newdir}. File not found.") + else: + print("Illegal switch, Command Format: DEL[/s] [path][file]") + else: + print(f"Unable to delete: {tmpDir}{newdir}. File not found.") + else: + print("Illegal Path.") + + elif cmd in ["TYPE","MORE"]: + + if len(args) == 2: + if validPath and newdir in os.listdir(pFmt(tmpDir,False)) and aFile(tmpDir+newdir): + if cmd == "MORE": + swBits = swBits | int('000100',2) + + if not ((swAllB-int('000100',2)) & swBits): + swPause = bool(swBits & int('000100',2)) + key = " " + with open(tmpDir+newdir, "rb") as f: + nLines = 0 + for line in f: + istrt = 0 + while istrt+scrWdth < len(line): + (quit,nLines) = scrnPause(swPause,nLines,[None]) + try: + print(line[istrt:istrt+scrWdth].decode()) + except: + print(chr(65534)*scrWdth) + nLines += 1 + istrt += scrWdth + (quit,nLines) = scrnPause(swPause,nLines,[None]) + if quit: + break + try: + print(line[istrt:len(line)].decode(),end="") + except: + print(chr(65534)*(len(line)-istrt)) + nLines += 1 + else: + print("Illegal switch, Command Format: TYPE[/p] [path][file]") + else: + print(f"Unable to display: {tmpDir}{newdir}. File not found.") + else: + print("Illegal Path.") + + + elif cmd in ["CHDIR","CD"]: + + if len(args) == 1: + print(os.getcwd().replace('/',('\\' if envVars.get('DIRSEP','/') == '\\' else '/'))) + else: + if validPath: + os.chdir(tmpDir) + else: + print("Unable to change to:",args[1]) + + elif cmd in ["MKDIR","MD"]: + if len(args) == 1: + print("Unable to make .") + elif len(args) > 2: + print("Too many arguments") + else: + if validPath: + if newdir not in os.listdir(pFmt(tmpDir,False)): + os.mkdir(tmpDir+newdir) + else: + print("Target name already exists") + else: + print("Invalid path") + + elif cmd in ["RMDIR","RD","DELTREE"]: + if len(args) == 2 and validPath: + if cmd == "DELTREE": + swBits = int('000010',2) + + if not (swBits & (swAllB-int('000010',2))): + if tmpDir != sep: + if swBits & int('000010',2): + ans = input(tmpDir+" Are you sure (y/n)? ").upper() + if ans == "Y": + delFiles(tmpDir+sep,'*',bool(swBits&int('000010',2)),True) + + if os.listdir(tmpDir) == []: + if os.getcwd() == tmpDir: + os.chdir("..") + if len(tmpDir.split(sep)) > 2: + os.chdir(sep+tmpDir.split(sep)[1]) + try: + os.rmdir(tmpDir) + except Exception as err: + print(f"Unable to remove: {tmpDir}, Exception: {err}") + try: + os.chdir(savDir) + except: + pass + else: + print("The directory is not empty") + else: + print("Can not remove root directory") + else: + print("Illegal switch, Command Format: RD[/s] path") + else: + print("Invalid path") + + elif cmd == "COPY": + + if (len(args) == 3 or len(args) == 2) and not swBits & (swAllB-int('00001000',2)): + if len(args) == 2: + args.append('.') + + nFiles = 0 + earlyError = False + trailingSlash = False + if args[1][-1] == sep: + print("The source file cannot be a directory") + earlyError = True + if args[2][-1] == sep: + trailingSlash = True + +# Check that first argument has a valid path, exists and is not a directory file + if not earlyError and validPath and ("*" in newdir or "?" in newdir or \ + (newdir in os.listdir(tmpDir) and aFile(pFmt(tmpDir)+newdir))): + + sourcePath = tmpDir + if "*" in newdir or "?" in newdir: + wildCardOp = True + else: + wildCardOp = False + + enteredArg = args[2] + args[2] = absolutePath(args[2],savDir) + aPath2 = args[2].split(sep) + newdir2 = aPath2.pop(-1) + (validPath, tmpDir) = chkPath(aPath2) + if newdir2 == "*": + newdir2 = "." + if "*" in newdir2 or "?" in newdir2: + validPath = False + +# Second argument has valid path + if validPath: + gc.collect() + targetPath = tmpDir + + if newdir2 in "..": + (validPath,tmpDir) = chkPath((pFmt(targetPath)+newdir2).split(sep)) + aPath2 = tmpDir.split(sep) + newdir2 = aPath2.pop(-1) + targetPath = chkPath(aPath2)[1] + if not validPath: + print("Impossible Target Error:",args[2]) + +# Second argument specifies an existing target + if newdir2 in os.listdir(targetPath) or (targetPath == sep and newdir2 == ""): + +# Second argument target is a file + if aFile(pFmt(targetPath)+newdir2): + if trailingSlash: + print("Cannot find the specified path: ",enteredArg) + elif wildCardOp: + print("Target must be directory for wildcard copy ",enteredArg) + + elif sourcePath == targetPath and newdir == newdir2: + print("The file cannot be copied onto itself") + + elif swBits & int('001000',2) or input("Overwrite "+args[2]+"? (y/n): ").upper() == "Y": + os.remove(pFmt(targetPath)+newdir2) + filecpy(pFmt(sourcePath)+newdir,pFmt(targetPath)+newdir2) + nFiles += 1 + +# Second argument target is a directory + else: + sourcePath = pFmt(sourcePath) + targetPath = pFmt(targetPath) + + if wildCardOp: + ans = "" + for _dir in os.listdir(pFmt(sourcePath,False)): + if aFile(sourcePath+_dir) and _match(newdir,_dir[:wldCLen]): + print("copy",sourcePath+_dir,"to",pFmt(targetPath+newdir2)+_dir) + if _dir in os.listdir(targetPath+newdir2): + if sourcePath == pFmt(targetPath+newdir2): + print("The file cannot be copied onto itself") + break + else: + if ans != "A" and not swBits & int('001000',2): + ans = input("Overwrite "+pFmt(targetPath+newdir2)+_dir+"? (y/n/(q)uit/(a)ll): ").upper() + if ans == "Y" or ans == "A" or swBits & int('001000',2): + filecpy(sourcePath+_dir,pFmt(targetPath+newdir2)+_dir) + nFiles += 1 + elif ans == "Q": + break + else: + filecpy(sourcePath+_dir,pFmt(targetPath+newdir2)+_dir) + nFiles += 1 + gc.collect() + + elif newdir in os.listdir(targetPath+newdir2): + if sourcePath == pFmt(targetPath+newdir2): + print("The file cannot be copied onto itself") + elif swBits & int('001000',2) or input("Overwrite "+pFmt(targetPath+newdir2)+newdir+"? (y/n): ").upper() == "Y": + os.remove(pFmt(targetPath+newdir2)+newdir) + filecpy(sourcePath+newdir,pFmt(targetPath+newdir2)+newdir) + nFiles += 1 + + else: + filecpy(sourcePath+newdir,pFmt(targetPath+newdir2)+newdir) + nFiles += 1 +# Second argument is a new file + else: + if trailingSlash or wildCardOp: + if wildCardOp: + print("Target must be directory for wildcard copy ",enteredArg) + else: + print("Cannot find the specified path: ",enteredArg) + else: + filecpy(pFmt(sourcePath)+newdir,pFmt(targetPath)+newdir2) + nFiles += 1 + + print(" "*7,nFiles,"files(s) copied.") + else: + print("Invalid destination:",args[2]) + else: + print("No such file:",args[1]) + else: + if swBits & (swAllB-int('001000',2)): + print("Illegal switch, Command Format: COPY[/y] [path]file [path][file]") + else: + print("Wrong number of arguments") + + elif cmd == "PEXEC": + if not (swAllB-int('1000000',2)) & swBits: + if len(args) == 1: + pcmd = input("=>> ") + else: + pcmd = " ".join(args[1:]) + try: + exec(pcmd) + envVars['errorlevel'] = '0' + except Exception as err: + if not swBits & int('1000000',2): + print("*ERROR* Exception:",str(err)) + envVars['errorlevel'] = '1' + del pcmd + else: + print("Illegal switch, Command Format: PEXEC[/q] python command") + + elif cmd == "EXIT": + if activeBAT: + if len(args) > 1: + envVars['errorlevel'] = args[1] + activeBAT = False + batEcho = True + BATfile.close() + else: + threadFound = True + try: + testthreadLock = threadLock + except: + threadFound = False + + if threadFound: + import _thread + + if threadLock.locked(): + threadLock.release() + else: + threadLock.acquire() + + break + + else: + savDir = os.getcwd() + args[0] = absolutePath(args[0],savDir) + + aPath = args[0].split(sep) + if 'xcopy' in aPath: + envVars["_switches"] = [i.upper() for i in aPath[aPath.index('xcopy')+1:]] + aPath = aPath[0:aPath.index('xcopy')+1] + newdir = aPath.pop(-1) + (validPath, tmpDir) = chkPath(aPath) + tmpDir = pFmt(tmpDir) + + args = [('"'+i+'"' if i.find(" ") != -1 else i) for i in args] + if len(args) == 1: + passedIn = "" + batParams = [] + elif len(args) > 1: + passedIn = " ".join(args[1:]) + batParams = args[1:] + + gc.collect() + batFound = "" + + cmdFound = False + for tmpDir in [tmpDir]+(envVars.get('PATH',"").split(";") if envVars.get('PATH',"") != "" else []): + tmpDir = pFmt(tmpDir) + try: + curDLst = os.listdir(pFmt(tmpDir,False)) + except: + curDLst = [] + curDLst_LC = ",".join(curDLst).lower().split(",") + if ((newdir.split("."))[-1]).upper() == "PY": + if validPath and newdir in curDLst and aFile(tmpDir+newdir): + + exCmd(tmpDir+newdir,passedIn) + cmdFound = True + break + elif ((newdir.split("."))[-1]).upper() == "BAT": + if validPath and newdir in curDLst and aFile(tmpDir+newdir): + batFound = newdir + break + elif validPath: + if newdir.lower()+".py" in curDLst_LC: + curDLst_indx = curDLst_LC.index(newdir.lower()+".py") + if aFile(tmpDir+curDLst[curDLst_indx]): + exCmd(tmpDir+curDLst[curDLst_indx],passedIn) + cmdFound = True + break + elif newdir.lower()+".bat" in curDLst_LC: + curDLst_indx = curDLst_LC.index(newdir.lower()+".bat") + if aFile(tmpDir+curDLst[curDLst_indx]): + batFound = curDLst[curDLst_indx] + break + + if '_switches' in envVars.keys(): + envVars.pop('_switches') + + if batFound != "": + if activeBAT: + BATfile.close() + BATfile = open(tmpDir+batFound) + activeBAT = True + batEcho = True + batLabels = readBATFile(BATfile) + batLineNo = 0 + elif not cmdFound: + print("Illegal command:",cmdLine.split(" ")[0]) + + gc.collect() + +PyDOS() +reload() + diff --git a/builtin_apps/PyDOS/metadata.json b/builtin_apps/PyDOS/metadata.json new file mode 100644 index 0000000..07d9332 --- /dev/null +++ b/builtin_apps/PyDOS/metadata.json @@ -0,0 +1,4 @@ +{ + "title": "PyDOS", + "icon": "PyDOS.bmp" +} diff --git a/builtin_apps/virtrepl/code.py b/builtin_apps/virtrepl/code.py new file mode 100644 index 0000000..bd8f93f --- /dev/null +++ b/builtin_apps/virtrepl/code.py @@ -0,0 +1,46 @@ +import os +from supervisor import reload +try: + from codeop import compile_command +except: + pass + +print("REPL in (Circuit-)Python (type exit to close)") + +__cmd = "" +while True: + if 'compile_command' in dir(): +# 9.0.0 alpha 7 or later supports multiple line statements + __line = input(",,, " if __cmd else "=>> " ) + + if __cmd: + __cmd += ("\n" + (" " if __line != "" else "") + __line) + else: + if __line.lower() == 'exit': + break + __cmd = __line + try: + if compile_command(__cmd): + exec(compile_command(__cmd)) + __cmd = "" + except Exception as __err: + print("*ERROR* Exception:",str(__err)) + __cmd = "" + else: +# Pre 9.0.0 alpha 7 code or Micropython (single line statments only) + __line = input("=>> ") + if __line.lower() == 'exit': + break + __result = None + try: + exec('__result='+__line) + except: + try: + exec(__line) + except Exception as err: + print("*ERROR* Exception:",str(err)) + + if __result != None and __line.find('=') == -1: + print(__result) + +reload() diff --git a/builtin_apps/virtrepl/metadata.json b/builtin_apps/virtrepl/metadata.json new file mode 100644 index 0000000..04f278a --- /dev/null +++ b/builtin_apps/virtrepl/metadata.json @@ -0,0 +1,4 @@ +{ + "title": "Virtual REPL", + "icon": "virtrepl.bmp" +} diff --git a/builtin_apps/virtrepl/virtrepl.bmp b/builtin_apps/virtrepl/virtrepl.bmp new file mode 100644 index 0000000..488a82d Binary files /dev/null and b/builtin_apps/virtrepl/virtrepl.bmp differ