mirror of
https://git.srhtnin.garden/sarah/SaraSort.git
synced 2026-10-14 10:48:11 +02:00
Flow edit mode: edit arrow rules, delete directories
Each rule on an arrow can be edited (pattern, NewFileName, case-sensitive, overwrite); a rule shared with other inputs is split so only this arrow changes. Connecting an input with a pattern an output already has no longer renames it for the other inputs. Boxes get a delete button that removes the directory from SaraSortd (GlobalConf, other outputs' rules, its config and history files) but never the folder itself. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
1 parent
60da6dd9ab
commit
918e1d68cf
3 files changed
+232
-37
No files matched your search
@@ -427,14 +427,21 @@ class Setup:
|
||||
OutDoc["Files"] = AoT([])
|
||||
Files = OutDoc["Files"]
|
||||
Defaults = self.TemplateFileDefaults()
|
||||
Desired = {"NewFileName": Defaults["NewFileName"], **(EntryOverrides or {})}
|
||||
Splits: list[str] = [] # patterns this input already gets, but named differently now
|
||||
for Pattern in Patterns:
|
||||
Existing = next((Entry for Entry in Files if Entry.get("Pattern") == Pattern), None)
|
||||
if Existing is not None:
|
||||
Own = Existing.get("ValidInputDirs")
|
||||
if Own is not None and not self._EntryAccepts(Existing.unwrap(), OutputPath, InputPath):
|
||||
Own.append(self.Shorthand(InputPath))
|
||||
for Key, Value in (EntryOverrides or {}).items():
|
||||
Existing[Key] = Value
|
||||
Current = next((Entry for Entry in Files if Entry.get("Pattern") == Pattern
|
||||
and self._EntryAccepts(Entry.unwrap(), OutputPath, InputPath)), None)
|
||||
if Current is not None:
|
||||
if EntryOverrides and any(Current.get(Key) != Value for Key, Value in EntryOverrides.items()):
|
||||
Splits.append(Pattern)
|
||||
continue
|
||||
# An entry other inputs use with the same pattern and naming can take this input too.
|
||||
Reusable = next((Entry for Entry in Files if Entry.get("Pattern") == Pattern
|
||||
and isinstance(Entry.get("ValidInputDirs"), list)
|
||||
and all(Entry.get(Key) == Value for Key, Value in Desired.items())), None)
|
||||
if Reusable is not None:
|
||||
Reusable["ValidInputDirs"].append(self.Shorthand(InputPath))
|
||||
continue
|
||||
Entry = tomlkit.table()
|
||||
Entry["Pattern"] = Pattern
|
||||
@@ -446,6 +453,11 @@ class Setup:
|
||||
Entry["ValidInputDirs"] = [self.Shorthand(InputPath)]
|
||||
Files.append(Entry)
|
||||
WriteToml(self.ConfFile(OutputPath, "Out"), OutDoc)
|
||||
for Pattern in Splits:
|
||||
Entries = (self.LoadDirConf(OutputPath, "Out") or {}).get("Files", [])
|
||||
Index = next(Position for Position, Entry in enumerate(Entries) if Entry.get("Pattern") == Pattern
|
||||
and self._EntryAccepts(Entry, OutputPath, InputPath))
|
||||
self.EditRule(InputPath, OutputPath, Index, dict(EntryOverrides))
|
||||
|
||||
# The input side: it has to let these files through at all.
|
||||
InDoc, IsNew = self.EnsureDirConf(InputPath, "In")
|
||||
@@ -511,6 +523,105 @@ class Setup:
|
||||
Messages.append(Message)
|
||||
return Messages
|
||||
|
||||
def RuleInputs(self, OutputPath: str, Index: int) -> list[str]:
|
||||
"""The inputs the [[Files]] entry at Index takes files from."""
|
||||
OutConf = self.LoadDirConf(OutputPath, "Out") or {}
|
||||
Entries = OutConf.get("Files", [])
|
||||
if Index >= len(Entries):
|
||||
return []
|
||||
return [Input for Input in self.ConnectedInputs(OutputPath) if self._EntryAccepts(Entries[Index], OutputPath, Input)]
|
||||
|
||||
def EditRule(self, InputPath: str, OutputPath: str, Index: int, Changes: dict) -> Optional[str]:
|
||||
"""Changes (Pattern, NewFileName, CaseSensitive, Overwrite) the [[Files]]
|
||||
entry at Index for the arrow InputPath -> OutputPath. An entry other
|
||||
inputs use too is split: this arrow gets its own changed copy just
|
||||
before it (so it matches first), the others keep the original.
|
||||
Returns a note when it couldn't be split."""
|
||||
ConfPath = self.ConfFile(OutputPath, "Out")
|
||||
Document = ReadToml(ConfPath)
|
||||
Files = Document.get("Files")
|
||||
if Files is None or Index >= len(Files):
|
||||
return "That rule no longer exists."
|
||||
Entry = Files[Index]
|
||||
Others = [Input for Input in self.RuleInputs(OutputPath, Index) if Input != InputPath]
|
||||
Message = None
|
||||
if Others:
|
||||
Own = Entry.get("ValidInputDirs")
|
||||
Remaining = None
|
||||
if Own is None:
|
||||
Remaining = [self.Shorthand(Other) for Other in Others]
|
||||
else:
|
||||
Parent = os.path.basename(OutputPath)
|
||||
Remaining = [Valid for Valid in Own if self.Parse(str(Valid), Parent=Parent) != InputPath]
|
||||
Test = dict(Entry.unwrap(), ValidInputDirs=Remaining)
|
||||
if self._EntryAccepts(Test, OutputPath, InputPath):
|
||||
Remaining = None # a wildcard still matches this input: can't split it off
|
||||
Message = (f"{Entry.get('Pattern')} reaches this input through a wildcard, so the change "
|
||||
f"applies to {', '.join(os.path.basename(Other) for Other in Others)} too.")
|
||||
if Remaining is not None:
|
||||
Copy = tomlkit.table()
|
||||
for Key, Value in Entry.unwrap().items():
|
||||
Copy[Key] = Value
|
||||
for Key, Value in Changes.items():
|
||||
Copy[Key] = Value
|
||||
Copy["ValidInputDirs"] = [self.Shorthand(InputPath)]
|
||||
Entry["ValidInputDirs"] = Remaining
|
||||
Files.insert(Index, Copy)
|
||||
WriteToml(ConfPath, Document)
|
||||
return None
|
||||
for Key, Value in Changes.items():
|
||||
Entry[Key] = Value
|
||||
WriteToml(ConfPath, Document)
|
||||
return Message
|
||||
|
||||
def DeleteDirectory(self, PathText: str) -> bool:
|
||||
"""Takes a directory out of SaraSortd completely: out of InputDir and
|
||||
OutputDir, out of other outputs' ValidInputDirs (entries left with no
|
||||
input are removed), and its config and history files are deleted.
|
||||
The directory and its files stay. True if GlobalConf changed."""
|
||||
for OutputPath in dict.fromkeys(self.Outputs):
|
||||
if OutputPath == PathText:
|
||||
continue
|
||||
ConfPath = self.ConfFile(OutputPath, "Out")
|
||||
try:
|
||||
Document = ReadToml(ConfPath)
|
||||
except (OSError, tomlkit.exceptions.TOMLKitError):
|
||||
continue
|
||||
Changed = False
|
||||
DirValid = Document.get("ValidInputDirs")
|
||||
if isinstance(DirValid, list):
|
||||
Parent = Document.get("ParentDir")
|
||||
for Position in reversed(range(len(DirValid))):
|
||||
if self.Parse(str(DirValid[Position]), Parent=Parent) == PathText:
|
||||
del DirValid[Position]
|
||||
Changed = True
|
||||
Files = Document.get("Files")
|
||||
Parent = os.path.basename(OutputPath)
|
||||
for Index in reversed(range(len(Files) if Files is not None else 0)):
|
||||
Own = Files[Index].get("ValidInputDirs")
|
||||
if not isinstance(Own, list):
|
||||
continue
|
||||
Before = len(Own)
|
||||
for Position in reversed(range(Before)):
|
||||
if self.Parse(str(Own[Position]), Parent=Parent) == PathText:
|
||||
del Own[Position]
|
||||
if len(Own) != Before:
|
||||
Changed = True
|
||||
if len(Own) == 0:
|
||||
del Files[Index]
|
||||
if Changed:
|
||||
WriteToml(ConfPath, Document)
|
||||
|
||||
GlobalChanged = self.RemoveDir(PathText)
|
||||
Parent = os.path.basename(PathText)
|
||||
Leftovers = {self.ConfFile(PathText, "In"), self.ConfFile(PathText, "Out")}
|
||||
HistoryName = self.Names.get("FileHistoryName")
|
||||
if HistoryName:
|
||||
Leftovers.add(Path(PathText) / f"{self.Parse(str(HistoryName), Parent=Parent)}.toml")
|
||||
for FilePath in Leftovers:
|
||||
FilePath.unlink(missing_ok=True)
|
||||
return GlobalChanged
|
||||
|
||||
# What would happen to a file
|
||||
|
||||
def Simulate(self, InputPath: str, FileName: str) -> SimulationResult:
|
||||
|
||||
Reference in new issue
Block a user