From 7f9a66b5c421676a311c23533c4b6f6a1195f99d Mon Sep 17 00:00:00 2001 From: RetiredWizard Date: Wed, 13 Aug 2025 11:41:31 -0400 Subject: [PATCH] Editor scrolling fix, PyDOS updates --- builtin_apps/PyDOS/PyDOS.py | 1387 +++++++++++++++++ builtin_apps/PyDOS/code.py | 1386 +--------------- builtin_apps/editor/adafruit_editor/editor.py | 12 +- 3 files changed, 1400 insertions(+), 1385 deletions(-) create mode 100644 builtin_apps/PyDOS/PyDOS.py diff --git a/builtin_apps/PyDOS/PyDOS.py b/builtin_apps/PyDOS/PyDOS.py new file mode 100644 index 0000000..b414cdf --- /dev/null +++ b/builtin_apps/PyDOS/PyDOS.py @@ -0,0 +1,1387 @@ +import os +try: + from os import sep +except: + sep = os.getcwd()[0] +from time import localtime,struct_time +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(f'{sep}apps{sep}PyBasic') +try: + from pydos_ui import Pydos_ui +except ImportError: + Pydos_ui = None +try: + from pydos_ui import input +except ImportError: + pass +try: + from rtc import RTC + clockavail = True +except ImportError: + clockavail = False + +import gc +imp = "B" +if implementation.name.upper() == "MICROPYTHON": + from micropython import mem_info + readonly = False + imp = "M" +elif implementation.name.upper() == "CIRCUITPYTHON": + if not Pydos_ui: + from supervisor import runtime + + from supervisor import reload + import storage + import microcontroller + import displayio + from adafruit_argv_file import argv_filename + readonly = storage.getmount("/").readonly + + 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.54-fruitjam" + prmpVals = ['>','(',')','&','|','\x1b','\b','<','=',' ',_VER,'\n','$',''] + + print("Starting Py-DOS... Type 'help' for help.") + if readonly: + print("Warning: Py-DOS is running in read-only mode, some commands may not work.") + ans = 'N' + while ans[:1].upper() != "C" and ans[:1].upper() != "R": + ans = input("Press 'C' to continue or 'R' to restart in read-write mode: ") + if ans[:1].upper() == "R": + print("\nNote: You can not modify files using the CIRCUITPY drive from a") + print("connected host computer in read-write mode. After restarting,") + print("you can use the 'readonly' command in PyDOS to return to read-only mode.\n") + if input('Are you sure? (Y/N): ').upper() == 'Y': + boot_args_file = argv_filename("/boot.py") + with open(boot_args_file, "w") as f: + f.write('[false, "/code.py"]') + microcontroller.reset() + else: + break + elif ans[:1].upper() == "C": + break + else: + print("\nInvalid entry",ans[:1]) + + + envVars["PATH"] = f'{sep};{sep}apps{sep}PyDOS;{sep}apps{sep}PyBasic' + envVars["PROMPT"] = "$P$G" + envVars["LIB"] = ";".join(path[1:]) + envVars["DIRSEP"] = sep + if Pydos_ui: + (envVars["_scrHeight"],envVars["_scrWidth"]) = Pydos_ui.get_screensize() + else: + try: + envVars["_scrHeight"] = runtime.display.root_group[0].height + envVars["_scrWidth"] = runtime.display.root_group[0].width - 1 + except: + envVars["_scrHeight"] = 24 + envVars["_scrWidth"] = 89 + 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 setdate(newDate): + mdays = [0,31,29,31,30,31,30,31,31,30,31,30,31] + + if newDate != "": + inDate = newDate.split('-') + + if len(inDate) != 3: + print("Bad Date format") + elif int(inDate[0]) not in range(1,13): + print("Invalid month entered (1-12)") + elif int(inDate[1]) not in range(1,mdays[int(inDate[0])]+1): + print("invalid day entered (1-",mdays[int(inDate[0])],")") + elif int(inDate[2]) not in range(21,32): + print("invalid year entered (21-31)") + else: + RTC().datetime = struct_time((2000+int(inDate[2]),int(inDate[0]),int(inDate[1]), \ + RTC().datetime[3],RTC().datetime[4],RTC().datetime[5],RTC().datetime[6],-1,-1)) + + def settime(newTime): + if newTime != "": + inTime = newTime.split(':') + + if len(inTime) != 3: + print("Bad time format") + elif int(inTime[0]) not in range(0,25): + print("Invalid hour entered (0-24)") + elif int(inTime[1]) not in range(0,61): + print("invalid minute entered (0-60)") + elif int(inTime[2]) not in range(0,61): + print("invalid seconds entered (0-60)") + else: + RTC().datetime = struct_time((RTC().datetime[0],RTC().datetime[1],RTC().datetime[2], \ + int(inTime[0]),int(inTime[1]),int(inTime[2]),RTC().datetime[6],-1,-1)) + + 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) + if supervisor.runtime.display.root_group != displayio.CIRCUITPYTHON_TERMINAL: + supervisor.runtime.display.root_group = displayio.CIRCUITPYTHON_TERMINAL + except KeyboardInterrupt: + print("^C") + if supervisor.runtime.display.root_group != displayio.CIRCUITPYTHON_TERMINAL: + supervisor.runtime.display.root_group = displayio.CIRCUITPYTHON_TERMINAL + + 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','$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 == "HELP": + print("File Commands: DIR[/p][/w][/s], RENAME, DEL[/s], TYPE[/p], CD, MKDIR, RMDIR[/s], COPY[/y]") + print("Environment Commands: HELP, SET[/p][/a], PROMPT, PATH, READONLY") + print("Operating System Commands: EXIT, VER, MEM, DATE [mm-dd-yy], TIME [hh:mm:ss]") + print("Batch Commands: GOTO, IF, ECHO, PAUSE") + print("Command to execute a single Python command: PEXEC [command]") + print("Run a Python program: [path]program[.py]") + print("Run a DOS batch file: [path]program[.bat]") + 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": + if len(args) <=2: + if len(args) == 2: + if clockavail: + setdate(args[1]) + else: + print("Real Time Clock (rtc) not available on board") + i = localtime()[6]*3 + print(f'The current date is: {"MonTueWedThuFriSatSun"[i:i+3]} {localtime()[1]:02}/{localtime()[2]:02}/{localtime()[0]:04}') + else: + print("Too many arguments. Command Format: DATE [mm-dd-yy]") + + elif cmd == "TIME": + if len(args) <= 2: + if len(args) == 2: + if clockavail: + settime(args[1]) + else: + print("Real Time Clock (rtc) not available on board") + print(f'The current time is: {localtime()[3]%12}:{localtime()[4]:02}:{localtime()[5]:02} {["AM","PM"][localtime()[3]//12]}') + else: + print("Too many arguments. Command Format: TIME [hh:mm:ss]") + + 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:]) + + 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 = 29 + tWidth = 79 + 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 == "READONLY": + if imp != "C": + print("READONLY command is only available when running on CirucitPython.") + elif readonly: + print("The system is already set to read-only.") + else: + if input("Setting the system to read-only will cause the Fruit Jam to restart? (y/n): ").upper() == "Y": + boot_args_file = argv_filename("/boot.py") + with open(boot_args_file, "w") as f: + f.write('[true, "/code.py"]') + microcontroller.reset() + + 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/code.py b/builtin_apps/PyDOS/code.py index 59b23f8..b9f0d4e 100644 --- a/builtin_apps/PyDOS/code.py +++ b/builtin_apps/PyDOS/code.py @@ -1,1382 +1,6 @@ -import os -try: - from os import sep -except: - sep = os.getcwd()[0] -from time import localtime,struct_time -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(f'{sep}apps{sep}PyBasic') -try: - from pydos_ui import Pydos_ui -except ImportError: - Pydos_ui = None -try: - from pydos_ui import input -except ImportError: - pass -try: - from rtc import RTC - clockavail = True -except ImportError: - clockavail = False - -import gc -imp = "B" -if implementation.name.upper() == "MICROPYTHON": - from micropython import mem_info - readonly = False - imp = "M" -elif implementation.name.upper() == "CIRCUITPYTHON": - if not Pydos_ui: - from supervisor import runtime - - from supervisor import reload - import storage - import microcontroller - from adafruit_argv_file import argv_filename - readonly = storage.getmount("/").readonly - - 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.53-fruitjam" - prmpVals = ['>','(',')','&','|','\x1b','\b','<','=',' ',_VER,'\n','$',''] - - print("Starting Py-DOS... Type 'help' for help.") - if readonly: - print("Warning: Py-DOS is running in read-only mode, some commands may not work.") - ans = 'N' - while ans[:1].upper() != "C" and ans[:1].upper() != "R": - ans = input("Press 'C' to continue or 'R' to restart in read-write mode: ") - if ans[:1].upper() == "R": - print("\nNote: You can not modify files using the CIRCUITPY drive from a") - print("connected host computer in read-write mode. After restarting,") - print("you can use the 'readonly' command in PyDOS to return to read-only mode.\n") - if input('Are you sure? (Y/N): ').upper() == 'Y': - boot_args_file = argv_filename("/boot.py") - with open(boot_args_file, "w") as f: - f.write('[false, "/code.py"]') - microcontroller.reset() - else: - break - elif ans[:1].upper() == "C": - break - else: - print("\nInvalid entry",ans[:1]) - - - envVars["PATH"] = f'{sep};{sep}apps{sep}PyDOS;{sep}apps{sep}PyBasic' - envVars["PROMPT"] = "$P$G" - envVars["LIB"] = ";".join(path[1:]) - envVars["DIRSEP"] = sep - if Pydos_ui: - (envVars["_scrHeight"],envVars["_scrWidth"]) = Pydos_ui.get_screensize() - else: - try: - envVars["_scrHeight"] = runtime.display.root_group[0].height - envVars["_scrWidth"] = runtime.display.root_group[0].width - 1 - except: - envVars["_scrHeight"] = 24 - envVars["_scrWidth"] = 89 - 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 setdate(newDate): - mdays = [0,31,29,31,30,31,30,31,31,30,31,30,31] - - if newDate != "": - inDate = newDate.split('-') - - if len(inDate) != 3: - print("Bad Date format") - elif int(inDate[0]) not in range(1,13): - print("Invalid month entered (1-12)") - elif int(inDate[1]) not in range(1,mdays[int(inDate[0])]+1): - print("invalid day entered (1-",mdays[int(inDate[0])],")") - elif int(inDate[2]) not in range(21,32): - print("invalid year entered (21-31)") - else: - RTC().datetime = struct_time((2000+int(inDate[2]),int(inDate[0]),int(inDate[1]), \ - RTC().datetime[3],RTC().datetime[4],RTC().datetime[5],RTC().datetime[6],-1,-1)) - - def settime(newTime): - if newTime != "": - inTime = newTime.split(':') - - if len(inTime) != 3: - print("Bad time format") - elif int(inTime[0]) not in range(0,25): - print("Invalid hour entered (0-24)") - elif int(inTime[1]) not in range(0,61): - print("invalid minute entered (0-60)") - elif int(inTime[2]) not in range(0,61): - print("invalid seconds entered (0-60)") - else: - RTC().datetime = struct_time((RTC().datetime[0],RTC().datetime[1],RTC().datetime[2], \ - int(inTime[0]),int(inTime[1]),int(inTime[2]),RTC().datetime[6],-1,-1)) - - 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','$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 == "HELP": - print("File Commands: DIR[/p][/w][/s], RENAME, DEL[/s], TYPE[/p], CD, MKDIR, RMDIR[/s], COPY[/y]") - print("Environment Commands: HELP, SET[/p][/a], PROMPT, PATH, READONLY") - print("Operating System Commands: EXIT, VER, MEM, DATE [mm-dd-yy], TIME [hh:mm:ss]") - print("Batch Commands: GOTO, IF, ECHO, PAUSE") - print("Command to execute a single Python command: PEXEC [command]") - print("Run a Python program: [path]program[.py]") - print("Run a DOS batch file: [path]program[.bat]") - 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": - if len(args) <=2: - if len(args) == 2: - if clockavail: - setdate(args[1]) - else: - print("Real Time Clock (rtc) not available on board") - i = localtime()[6]*3 - print(f'The current date is: {"MonTueWedThuFriSatSun"[i:i+3]} {localtime()[1]:02}/{localtime()[2]:02}/{localtime()[0]:04}') - else: - print("Too many arguments. Command Format: DATE [mm-dd-yy]") - - elif cmd == "TIME": - if len(args) <= 2: - if len(args) == 2: - if clockavail: - settime(args[1]) - else: - print("Real Time Clock (rtc) not available on board") - print(f'The current time is: {localtime()[3]%12}:{localtime()[4]:02}:{localtime()[5]:02} {["AM","PM"][localtime()[3]//12]}') - else: - print("Too many arguments. Command Format: TIME [hh:mm:ss]") - - 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:]) - - 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 = 29 - tWidth = 79 - 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 == "READONLY": - if imp != "C": - print("READONLY command is only available when running on CirucitPython.") - elif readonly: - print("The system is already set to read-only.") - else: - if input("Setting the system to read-only will cause the Fruit Jam to restart? (y/n): ").upper() == "Y": - boot_args_file = argv_filename("/boot.py") - with open(boot_args_file, "w") as f: - f.write('[true, "/code.py"]') - microcontroller.reset() - - 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() +# Launches the PyDOS shell +# +# This is done in a sperate file so that __name__ is set to "PyDOS" instead of "__main__" +# which helps some modules detect that they are running in PyDOS. +import PyDOS diff --git a/builtin_apps/editor/adafruit_editor/editor.py b/builtin_apps/editor/adafruit_editor/editor.py index 8da0828..fa54197 100644 --- a/builtin_apps/editor/adafruit_editor/editor.py +++ b/builtin_apps/editor/adafruit_editor/editor.py @@ -259,6 +259,7 @@ def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: dis cursor = Cursor() terminal_tilegrid.pixel_shader[cursor.col,cursor.row] = [1, 0] old_cursor_pos = (cursor.col, cursor.row) + old_window_pos = (window.col, window.row) # try: # visible_cursor.text = buffer[0][0] # except IndexError: @@ -446,10 +447,12 @@ def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: dis # print("updating visible cursor") # print(f"anchored pos: {((cursor.col * 6) - 1, (cursor.row * 12) + 20)}") - if old_cursor_pos != (cursor.col, cursor.row): - # print(f"old cursor: {old_cursor_pos}, new: {(cursor.col, cursor.row)}") - terminal_tilegrid.pixel_shader[old_cursor_pos[0], old_cursor_pos[1]] = [0,1] - terminal_tilegrid.pixel_shader[cursor.col, cursor.row] = [1,0] + if (old_cursor_pos[0] - old_window_pos[0] != cursor.col - window.col or + old_cursor_pos[1] - old_window_pos[1] != cursor.row - window.row): + #print(f"old cursor: {old_cursor_pos}, new: {(cursor.col, cursor.row)}") + #print(f"window: {window.row}, {window.col}") + terminal_tilegrid.pixel_shader[old_cursor_pos[0] - old_window_pos[0], old_cursor_pos[1] - old_window_pos[1]] = [0,1] + terminal_tilegrid.pixel_shader[cursor.col - window.col, cursor.row - window.row] = [1,0] # print(f"old: {terminal_tilegrid.pixel_shader[old_cursor_pos[0], old_cursor_pos[1]]} new: {terminal_tilegrid.pixel_shader[cursor.col, cursor.row]}") # visible_cursor.anchored_position = ((cursor.col * 6) - 1, (cursor.row * 12) + 20) @@ -463,6 +466,7 @@ def editor(stdscr, filename, mouse=None, terminal_tilegrid=None): # pylint: dis old_cursor_pos = (cursor.col, cursor.row) + old_window_pos = (window.col, window.row) def edit(filename, terminal=None, mouse=None, terminal_tilegrid=None): with MaybeDisableReload():