import toml, os, datetime, re, shutil, fnmatch, time ConfPath = "./Config.toml" Conf = None ConfVars = None ConfDirs = None ConfNames = None ConfLog = None Start = True def Clear(): if os.name == 'nt': os.system('cls') else: os.system('clear') def LoadGlobalConf(): global Conf, ConfVars, ConfDirs, ConfNames, ConfLog LocalConf = toml.load(ConfPath) Conf = LocalConf ConfVars = Conf["Variables"] ConfDirs = Conf["DirectoryPaths"] ConfNames = Conf["Names"] ConfLog = Conf["Log"] def CheckConf(Path = ConfPath): ConfData = toml.load(Path) def CheckDict(Data): for Key, Value in Data.items(): if isinstance(Value, dict): CheckDict(Value) else: if Value is None or Value == "Unset": TextOutput = Parse(String = ConfLog["Unset"], Path = Path, VarCall = Key) LogWrite(TextOutput) Speak(TextOutput) Error() CheckDict(ConfData) def GetConf(Parameter, *Paths): for Path in Paths: ConfData = toml.load(Path) if Parameter in ConfData: return (ConfData[Parameter]) for Key, Value in ConfData.items(): if (isinstance(Value, dict)): if (Parameter in Value): return Value[Parameter] elif (isinstance(Value, list)): for Item in Value: if (isinstance(Item, dict) and Parameter in Item): return Item[Parameter] return None def UpdateConf(Path, Parameter, Value, Append = False): ConfData = toml.load(Path) def UpdateInData(Data): if isinstance(Data, dict): if Parameter in Data: if (not Append): Data[Parameter] = Value else: Data.setdefault(Parameter, []) if (Value not in Data[Parameter]): Data[Parameter].append(Value) return True else: for Key, DataValue in Data.items(): if (UpdateInData(DataValue)): return True elif (isinstance(Data, list)): for Item in Data: if (UpdateInData(Item)): return True return False Updated = UpdateInData(ConfData) if (not Updated): if not Append: ConfData[Parameter] = Value else: ConfData.setdefault(Parameter, []) if (Value not in ConfData[Parameter]): ConfData[Parameter].append(Value) with open(Path, 'w', encoding='utf-8') as File: toml.dump(ConfData, File) TextOutput = Parse(String = ConfLog["ValueSet"], VarCall = f"{Parameter} to {Value}") LogWrite(TextOutput) Speak(TextOutput) def Error(): global Start if (Conf["SafeMode"] == 1): Start = False def Speak(Text): if (Conf["SilentMode"] != 1): print(Text) def Parse(String = None, Path = ConfPath, NextNum = None, NextChar = None, Parent = None, OrgFile = None, VarCall = None): def Replacer(Match): Content = Match.group(1) if (Content == None or Content == ""): if (VarCall != None): return str(VarCall) else: return "" Value = GetConf(Content, Path) if (Value != None): return Value Now = datetime.datetime.now() if (OrgFile != None): OrgFileName, OrgFileType = os.path.splitext(OrgFile) else: OrgFileName = None OrgFileType = None VarValues = { ConfVars["NextNum"]: str(NextNum), ConfVars["NextChar"]: str(NextChar), ConfVars["Parent"]: str(Parent), ConfVars["OrgFileName"]: str(OrgFileName), ConfVars["OrgFileType"]: str(OrgFileType), ConfVars["Year"]: str(Now.year), ConfVars["Month"]: str(Now.month), ConfVars["Day"]: str(Now.day), ConfVars["Hour"]: str(Now.hour), ConfVars["Minute"]: str(Now.minute), ConfVars["Second"]: str(Now.second), ConfVars["VarCall"]: str(VarCall) } for Key, Value in VarValues.items(): if Key in String and Value is not None: String = String.replace(Key, Value) VarCall0 = re.escape(ConfVars["VarCall"][0]) VarCall1 = re.escape(ConfVars["VarCall"][1]) Pattern = f"{VarCall0}(.*?){VarCall1}" String = re.sub(Pattern, Replacer, String) return String def Clone(Source, Destination, NewName, Delete = False): Dir(Destination, Output = False, CopyConf = False) shutil.copy2(Source, Destination) BaseName = os.path.basename(Source) shutil.move(os.path.join(Destination, BaseName), os.path.join(Destination, NewName)) if (Delete): os.remove(Source) def Dir(Path, Output = True, CopyConf = True): ParentName = os.path.basename(Path) os.makedirs(Path, exist_ok=True) if (CopyConf): Clone("DirConfig.toml", Path, f"{Parse(String = ConfNames["DirConfName"], Path = ConfPath, Parent = ParentName)}.toml") UpdateConf(f"{Path}/{Parse(String = ConfNames["DirConfName"], Path = ConfPath, Parent = ParentName)}.toml", "ParentDir", ParentName) UpdateConf(f"{Path}/{Parse(String = ConfNames["DirConfName"], Path = ConfPath, Parent = ParentName)}.toml", "Title", f"{ParentName} Config") if (Output): ParsedPath = Parse(String = Path) ParsedOutputs = [Parse(String = Dir) for Dir in ConfDirs["OutputDir"]] if (ParsedPath not in ParsedOutputs): UpdateConf(ConfPath, "OutputDir", Path, True) def LogWrite(Text): if (GetConf("Logging", ConfPath) == 1): if (ConfDirs["LogDir"] != None and ConfNames != "Unset"): if (ConfNames["LogFileName"] != None and ConfNames["LogFileName"] != "Unset"): Dir(Parse(String = ConfDirs["LogDir"], Path = ConfPath), Output = False, CopyConf = False) LogFile = f"{Parse(String = ConfDirs["LogDir"], Path = ConfPath)}/{Parse(String = ConfNames["LogFileName"], Path = ConfPath)}.log" with open(LogFile, "a", encoding="utf-8", buffering=1) as File: File.write(f"{Parse(String = ConfLog["All"], Path = ConfPath)}{Text}\n") def DecideNewPath(FilePath): UnsortedFile = os.path.basename(FilePath) for Output in ConfDirs["OutputDir"]: Output = Parse(String = Output, Path = ConfPath) ParentName = os.path.basename(Output) DirConfName = Parse(String = ConfNames["DirConfName"], Path = ConfPath, Parent = ParentName) DirConfPath = f"{Output}/{DirConfName}.toml" if not os.path.exists(DirConfPath): continue FileConf = GetConf("Files", DirConfPath) if not FileConf: continue for File in FileConf: Pattern = File["Pattern"] TextOutput = Parse(ConfLog["MatchPattern"], VarCall = f"{UnsortedFile} against {Pattern}") LogWrite(TextOutput) Speak(TextOutput) if (File["CaseSensitive"] == 1): Match = fnmatch.fnmatchcase(UnsortedFile, Pattern) else: Match = fnmatch.fnmatch(UnsortedFile.lower(), Pattern.lower()) if Match: NewFileName = File["NewFileName"] if (ConfVars["NextNum"] in NewFileName): UpdateConf(DirConfPath, "NextNum", (int(GetConf("NextNum", DirConfPath)) + 1)) if (ConfVars["NextChar"] in NewFileName): CurrentChar = GetConf("NextChar", DirConfPath) if (CurrentChar and len(CurrentChar) == 1 and CurrentChar.isalpha()): NewChar = chr(((ord(CurrentChar.upper()) - 65 +1) % 26) +65) UpdateConf(DirConfPath, "NextChar", NewChar) NewFileName = Parse(String = NewFileName, Path = DirConfPath, OrgFile = UnsortedFile, NextNum = File["NextNum"], NextChar = File["NextChar"], Parent = ParentName) return f"{Output}/{NewFileName}" def Sort(FilePath): NewPath = DecideNewPath(FilePath) if (NewPath == None): Dir(Parse(String = ConfDirs["FailedDir"]), Output = False, CopyConf = False) NewName = os.path.basename(FilePath) Clone(FilePath, Parse(String = ConfDirs["FailedDir"]), NewName, True) TextOutput = Parse(String = ConfLog["NotSorted"], VarCall = FilePath) LogWrite(TextOutput) Speak(TextOutput) return NewDirPath = os.path.dirname(NewPath) NewName = os.path.basename(NewPath) try: Clone(FilePath, NewDirPath, NewName, True) TextOutput = Parse(String = ConfLog["Sorted"], VarCall = f"{FilePath} to {NewDirPath}/{NewName}") LogWrite(TextOutput) Speak(TextOutput) except Exception: Dir(Parse(String = ConfDirs["FailedDir"]), Output = False, CopyConf = False) NewName = os.path.basename(NewPath) Clone(FilePath, Parse(String = ConfDirs["FailedDir"]), NewName, True) TextOutput = Parse(String = ConfLog["NotSorted"], VarCall = FilePath) LogWrite(TextOutput) Speak(TextOutput) return def Init(): global Conf, ConfVars, ConfDirs, ConfNames, ConfLog LoadGlobalConf() if (Conf != None): CheckConf() if (Start): TextOutput = Parse(String = ConfLog["Start"], Path = ConfPath) LogWrite(TextOutput) Speak(TextOutput) Main() else: TextOutput = Parse(String = ConfLog["NotStart"]) LogWrite(TextOutput) Speak(TextOutput) else: print("NO CONFIG FOUND! Make sure the 'Config.toml' is in the same directory as 'SaraSortd.py'") def Main(): for Output in ConfDirs["OutputDir"]: Output = Parse(String = Output) Dir(Output) try: while True: for Input in ConfDirs["InputDir"]: InputPath = Parse(Input) if not os.path.exists(InputPath): continue for FileName in os.listdir(InputPath): FilePath = os.path.join(InputPath, FileName) if os.path.isfile(FilePath): FileName = os.path.basename(FilePath) if (FileName.startswith(".") and Conf["DotFiles"] == 0): continue Sort(FilePath) time.sleep(Conf.get("CheckInput", 10)) except KeyboardInterrupt: TextOutput = GetConf("Stop", ConfPath) LogWrite(TextOutput) Speak(TextOutput) Init()