From 51ecfe53b5f7ec03d1127eee168807ac0166dd82 Mon Sep 17 00:00:00 2001 From: Sarahtonin Date: Thu, 2 Apr 2026 22:01:24 +0200 Subject: [PATCH] Fixed most everything to work well enough. TODO: Add input folders, sorting, and output folders. --- Config.toml | 70 ++++++++-------- DirConfig.toml | 8 ++ README.md | 67 +++++++++++++++- SaraSortd.py | 212 +++++++++++++++++++++++++++++-------------------- 4 files changed, 229 insertions(+), 128 deletions(-) create mode 100644 DirConfig.toml diff --git a/Config.toml b/Config.toml index b0914b8..6b9cdab 100644 --- a/Config.toml +++ b/Config.toml @@ -1,47 +1,39 @@ Title = "SaraSortd Config" +SafeMode = 1 +SilentMode = 0 +CheckInput = 10 +OutputDir = [ "Log",] -SafeMode = 1 # If SaraSortd should stop if an error with the config appears. 1 is stop, 0 is continue. -SilentMode = 0 # If SaraSortd should write stuff to the terminal as well. - -[Variables] # Only use these in config files, not the actual file names themselves.. -NextNum = "*" # The next available number. If 1 already exists, 2 will be used. Starts at 1. -NextChar = "&" # The next available character. If A already exists, B will be used. - -Parent = "@" # The name of the parent directory. - -OrgFileName = "~" # The original name of the sorted file. If "blahblah.txt" gets sorted, "~" would be equal to "blahblah". -OrgFileType = "^" # The original file type of the sorted file. If "example.png" gets sorted, "^" would be equal to ".png". - -Year = "[y]" # The current year, E.G. "2026". -Month = "[m]" # The date of the current month, E.G. "03". -Day = "[d]" # The date of the current day, E.G. "27". - -Hour = "[h]" # The current hour, E.G. "12". -Minute = "[n]" # The current minute, E.G. "44". -Second = "[s]" # The current second, E.G. "11". - -VarCall = "%%" # The characters used at the start and end of variable calls. The first character is to the left; second is to the right. -# NOTE: If any parameters in the config uses just "%%", it gets a variable from the program itself, not from the config. +[Variables] +NextNum = "*" +NextChar = "&" +Parent = "@" +OrgFileName = "~" +OrgFileType = "^" +Year = "[y]" +Month = "[m]" +Day = "[d]" +Hour = "[h]" +Minute = "[n]" +Second = "[s]" +VarCall = "%%" [DirectoryPaths] -RootDir = "Unset" # Parent directory. Folder where all SaraSortd folders live. -InputDir = ["%RootDir%"] # If files get put here, they'll be sorted. -OutputDir = ["%RootDir%"] # Where sortted files go. -LogDir = "%RootDir%/.Logs" # Where log files from SaraSortd go. -FailedDir =".%RootDir%/Failed" # For files that failed to be sorted. +RootDir = "/home/sarah/TESTDIR" +InputDir = [ "%RootDir%",] +OutputDir = [ "%RootDir%/Output",] +LogDir = "%RootDir%/.Logs" +FailedDir = "%RootDir%/.Failed" [Names] -OutputFileName = "*_@^" # Name of sorted files. -LogFileName = "[y].[m].[d]_*" # Name of log files. -ConfFileName = ".@Config" # Name of config files for directories. +LogFileName = "[y].[m].[d]._*" +DirConfName = ".@Config" [Log] -All = "[h]:[n]:[s] | " # What to put at the start of all lines in Log file. - -Start = "Started: SaraSortd" # When SaraSortd starts. -NotStart = "Start Failed: SaraSortd." # When SaraSortd fails to start for some reason. - -Sorted = "Sorted: ~^ to %SortDir%." # When a file gets sorted. - -Unset = "Error: %% is not set." # When a parameter in the config is not set. %% is the parameter that wasn't set. -Set = "Set: %% to %%." # When a previously unset parameter in the config is set. %% is the parameter that got set. \ No newline at end of file +All = "[h]:[n]:[s] | " +Start = "Started: SaraSortd" +NotStart = "Error: Failed to start SaraSortd." +Sorted = "Sorted: %% to %%." +ValueSet = "Set: %% to be %% in %%." +WrongType = "Error: %% is not a %%." +Unset = "Error: %% is not set." \ No newline at end of file diff --git a/DirConfig.toml b/DirConfig.toml new file mode 100644 index 0000000..46473c5 --- /dev/null +++ b/DirConfig.toml @@ -0,0 +1,8 @@ +Title = "Unset" + +Files = [ + {Pattern = "?", NewFileName = "~^", NextNum = 1, NextChar = "A"}, +] + +LastFile = "Unset" +ParentDir = "Unset" diff --git a/README.md b/README.md index cf09f51..96ecc33 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,71 @@ # SaraSortd A python daemon that can automatically sort your files to your liking without using AI. + It's made by Sarah, hence the name. :3 ## Dependencies -toml: Use ```pip install toml``` to install toml. \ No newline at end of file +toml: Use ```pip install toml``` to install toml. + +## Config explanation + +### Global config (Config.toml) + +Title = "SaraSortd Config" # NOTE: Do not change the title. + +SafeMode = 1 # If SaraSortd should stop if an error with the config appears. 1 is stop, 0 is continue. +SilentMode = 0 # If SaraSortd should write stuff to the terminal as well. + +CheckInput = 10 # How many seconds it takes for SaraSortd to check input directories for new files to sort. + +[Variables] # Only use these in config files, not the actual file names themselves.. +NextNum = "*" # The next available number. If 1 already exists, 2 will be used. Starts at 1. +NextChar = "&" # The next available character. If A already exists, B will be used. + +Parent = "@" # The name of the parent directory. + +OrgFileName = "~" # The original name of the sorted file. If "blahblah.txt" gets sorted, "~" would be equal to "blahblah". +OrgFileType = "^" # The original file type of the sorted file. If "example.png" gets sorted, "^" would be equal to ".png". + +Year = "[y]" # The current year, E.G. "2026". +Month = "[m]" # The date of the current month, E.G. "03". +Day = "[d]" # The date of the current day, E.G. "27". + +Hour = "[h]" # The current hour, E.G. "12". +Minute = "[n]" # The current minute, E.G. "44". +Second = "[s]" # The current second, E.G. "11". + +VarCall = "%%" # The characters used at the start and end of variable calls. The first character is to the left; second is to the right. NOTE: If any parameters in the config uses just "%%", it gets a variable from the program itself, not from the config. + +[DirectoryPaths] +RootDir = "/home/sarah/TESTDIR" # Parent directory. Directory where all SaraSortd directories live. +InputDir = ["%RootDir%"] # If files get put here, they'll be sorted. +OutputDir = ["%RootDir%/Output"] # Where sortted files go. +LogDir = "%RootDir%/.Logs" # Where log files from SaraSortd go. +FailedDir ="%RootDir%/.Failed" # For files that failed to be sorted. + +[Names] +LogFileName = "[y].[m].[d]._*" # Name of log files. +DirConfName = ".@Config" # Name of config files for directories. + +[Log] +All = "[h]:[n]:[s] | " # What to put at the start of all lines in Log file. + +Start = "Started: SaraSortd" # When SaraSortd starts. +NotStart = "Error: Failed to start SaraSortd." # When SaraSortd fails to start for some reason. + +Sorted = "Sorted: %% to %%." # When a file gets sorted. +ValueSet = "Set: %% to be %% in %%." # When a value in a config is set. + +WrongType = "Error: %% is not a %%." # When a parameter in the config is of the wrong type. E.G. setting a text parameter to be a list. +Unset = "Error: %% is not set." # When a parameter in the config is not set. %% is the parameter that wasn't set. + +### Directory config (DirConfig.toml) + +Title = "Unset" + +Files = [ + {Pattern = "?", NewFileName = "~^", NextNum = 1, NextChar = "A"}, +] + +LastFile = "Unset" +ParentDir = "Unset" \ No newline at end of file diff --git a/SaraSortd.py b/SaraSortd.py index 0921b6a..dc17c0f 100644 --- a/SaraSortd.py +++ b/SaraSortd.py @@ -1,4 +1,4 @@ -import toml, os, datetime, re +import toml, os, datetime, re, shutil Conf = None ConfVars = None @@ -26,91 +26,7 @@ def LoadConf(): ConfLog = Conf["Log"] -def Error(): - global Start - if (Conf["SafeMode"] == 1): - Start = False - - -def Speak(Text): - if (Conf["SilentMode"] != 1): - print(Text) - - -def GetConf(Parameter): - if Parameter in Conf: - return str(Conf[Parameter]) - - elif Parameter in ConfVars: - return str(ConfVars[Parameter]) - - elif Parameter in ConfDirs: - return str(ConfDirs[Parameter]) - - elif Parameter in ConfNames: - return str(ConfNames[Parameter]) - - elif Parameter in ConfLog: - return str(ConfLog[Parameter]) - - return None - - -def Parse(String = None, Var = None, OrgFile = None, OutputDir = None): - def Replacer(Match): - Content = Match.group(1) - - if (Content == None or Content == ""): - if (Var != None): - return str(Var) - else: - return "" - - Value = GetConf(Content) - if (Value != None): - return Value - - - Now = datetime.datetime.now() - - VarValues = { - ConfVars["NextNum"]: "NOT ADDED", - ConfVars["NextChar"]: "NOT ADDED", - ConfVars["Parent"]: "NOT ADDED", - ConfVars["OrgFileName"]: "NOT ADDED", - ConfVars["OrgFileType"]: "NOT ADDED", - 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"]: Var - } - - for Key, Value in VarValues.items(): - if Key in String and Value is not None: - String = String.replace(Key, Value) - - String = re.sub(r"%([^%]*)%", Replacer, String) - - return String - - -def Dir(Name): - os.makedirs(Name, exist_ok=True) - - -def LogWrite(Text): - if (ConfDirs["LogDir"] != None and ConfNames != "Unset"): - if (ConfNames["LogFileName"] != None and ConfNames["LogFileName"] != "Unset"): - Dir(Parse(ConfDirs["LogDir"])) - LogFile = f"{Parse(ConfDirs["LogDir"])}/{Parse(ConfNames["LogFileName"])}.log" - with open(LogFile, "w", encoding="utf-8") as File: - File.write(f"{Parse(ConfLog["All"])}{Text}\n") - - -def ConfCheck(): +def CheckConf(): for Parameter, Value in ConfVars.items(): if (Value == None or Value == "Unset"): TextOutput = Parse(ConfLog["Unset"], Parameter) @@ -140,13 +56,133 @@ def ConfCheck(): Error() +def GetConf(Parameter): + if Parameter in Conf: + return (Conf[Parameter]) + + for Key, Value in Conf.items(): + if (isinstance(Value, dict)): + if (Parameter in Value): + return Value[Parameter] + + return None + + +def UpdateConf(Path, Parameter, Value, Append = False): + ConfData = toml.load(Path) + + if (not Append): + ConfData[Parameter] = Value + + else: + if (Parameter not in ConfData): + ConfData[Parameter] = [] + + elif (not isinstance(ConfData[Parameter], list)): + TextOutput = Parse(ConfLog["WrongType"], Parameter, "list") + LogWrite(TextOutput) + Speak(TextOutput) + + if (Value not in ConfData[Parameter]): + ConfData[Parameter].append(Value) + + with open(Path, 'w', encoding='utf-8') as File: + toml.dump(ConfData, File) + + +def Error(): + global Start + if (Conf["SafeMode"] == 1): + Start = False + + +def Speak(Text): + if (Conf["SilentMode"] != 1): + print(Text) + + +def Parse(String = None, Var = None, Path = None): + def Replacer(Match): + Content = Match.group(1) + + if (Content == None or Content == ""): + if (Var != None): + return str(Var) + else: + return "" + + Value = GetConf(Content) + if (Value != None): + return Value + + + Now = datetime.datetime.now() + + VarValues = { + ConfVars["NextNum"]: "NOT ADDED", + ConfVars["NextChar"]: "NOT ADDED", + ConfVars["Parent"]: Var, + ConfVars["OrgFileName"]: Var, + ConfVars["OrgFileType"]: Var, + 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"]: Var + } + + 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): + shutil.copy(Source, Destination) + + NewPath = f"{Destination}/{NewName}" + shutil.move(f"{Destination}/{Source}", NewPath) + + +def Dir(Path, Output = True, CopyConf = True): + ParentName = os.path.basename(os.path.dirname(Path)) + os.makedirs(Path, exist_ok=True) + + if (CopyConf): + Clone("DirConfig.toml", Path, f"{Parse(ConfNames["DirConfName"], ParentName)}.toml") + + UpdateConf(f"{Path}/{Parse(ConfNames["DirConfName"], ParentName)}.toml", "ParentDir", ParentName) + UpdateConf(f"{Path}/{Parse(ConfNames["DirConfName"], ParentName)}.toml", "Title", f"{ParentName} Config") + + if (Output): + UpdateConf("Config.toml", "OutputDir", Output, True) + + +def LogWrite(Text): + if (ConfDirs["LogDir"] != None and ConfNames != "Unset"): + if (ConfNames["LogFileName"] != None and ConfNames["LogFileName"] != "Unset"): + Dir(Parse(ConfDirs["LogDir"]), "Log") + LogFile = f"{Parse(ConfDirs["LogDir"])}/{Parse(ConfNames["LogFileName"])}.log" + with open(LogFile, "w", encoding="utf-8") as File: + File.write(f"{Parse(ConfLog["All"])}{Text}\n") + + def Init(): global Conf, ConfVars, ConfDirs, ConfNames, ConfLog LoadConf() if (Conf != None): - ConfCheck() - + CheckConf() + if (Start): TextOutput = Parse(ConfLog["Start"]) LogWrite(TextOutput)