UpdateMods,RenameMods,CleanLocales

This commit is contained in:
timerix 2023-04-05 04:07:39 +06:00
parent 49e7e21626
commit c449c5cb4d
5 changed files with 250 additions and 65 deletions

View File

@ -16,6 +16,6 @@
<ProjectReference Include="..\..\DTLib\DTLib\DTLib.csproj" /> <ProjectReference Include="..\..\DTLib\DTLib\DTLib.csproj" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(Configuration)' != 'Debug' "> <ItemGroup Condition=" '$(Configuration)' != 'Debug' ">
<PackageReference Include="DTLib" Version="1.2.0" /> <PackageReference Include="DTLib" Version="1.2.2" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -7,14 +7,13 @@ using DTLib.Ben.Demystifier;
namespace ParadoxModMerger; namespace ParadoxModMerger;
public record struct ConflictingModFile(string FilePath, string[] Mods);
public enum DiffState public enum DiffState
{ {
Added, Equal, Removed, Changed Added, Equal, Removed, Changed
} }
public record struct DiffPart<T>(T Value, DiffState State); public record struct DiffPart<T>(T Value, DiffState State);
public record struct ConflictingModFile(string FilePath, string[] Mods);
static class Diff static class Diff
{ {
@ -23,7 +22,7 @@ static class Diff
public static void DiffCommandHandler(string connected_pathes) public static void DiffCommandHandler(string connected_pathes)
{ {
IOPath[] moddirs = Program.SplitStringToPaths(connected_pathes); IOPath[] moddirs = Program.SplitArgToPaths(connected_pathes, false);
var conflicts = FindModConflicts(moddirs); var conflicts = FindModConflicts(moddirs);
LogModConflicts(conflicts); LogModConflicts(conflicts);
} }
@ -148,14 +147,14 @@ static class Diff
case ConsoleKey.LeftArrow: case ConsoleKey.LeftArrow:
{ {
ColoredConsole.Write("w", "enter left mod number: "); ColoredConsole.Write("w", "enter left mod number: ");
string answ = Console.ReadLine(); string answ = Console.ReadLine()!;
selected_mod0_i = answ.ToInt(); selected_mod0_i = answ.ToInt();
break; break;
} }
case ConsoleKey.RightArrow: case ConsoleKey.RightArrow:
{ {
ColoredConsole.Write("w", "enter right mod number: "); ColoredConsole.Write("w", "enter right mod number: ");
string answ = Console.ReadLine(); string answ = Console.ReadLine()!;
selected_mod1_i = answ.ToInt(); selected_mod1_i = answ.ToInt();
break; break;
} }
@ -194,7 +193,7 @@ static class Diff
public static IEnumerable<DiffPart<T>> DiffCollections<T>(ICollection<T> col0, ICollection<T> col1) public static IEnumerable<DiffPart<T>> DiffCollections<T>(ICollection<T> col0, ICollection<T> col1)
{ {
foreach (T el in col0) foreach (var el in col0)
yield return new DiffPart<T>(el, col1.Contains(el) ? DiffState.Equal : DiffState.Removed); yield return new DiffPart<T>(el, col1.Contains(el) ? DiffState.Equal : DiffState.Removed);
foreach (var el in col1) foreach (var el in col1)
if (!col0.Contains(el)) if (!col0.Contains(el))

View File

@ -23,4 +23,52 @@ static class Localisation
else Log("y", $"file {rusFileName} already exists"); else Log("y", $"file {rusFileName} already exists");
} }
} }
// deletes all localisations except l_russian and l_english
public static void Clean(IOPath _loc_dir)
{
Log("g", $"deleted {RemoveUnneededDirs(_loc_dir)} dirs");
Log("g", $"deleted {RemoveUnneededFiles(_loc_dir)} files");
int RemoveUnneededDirs(IOPath loc_dir)
{
int count = 0;
foreach (var subdir in Directory.GetDirectories(loc_dir))
{
string dir_basename = subdir.LastName().Str;
if (dir_basename == "russian" || dir_basename == "english")
continue;
if (dir_basename == "replace")
{
RemoveUnneededDirs(subdir);
RemoveUnneededFiles(subdir);
continue;
}
if (dir_basename.Contains("rus"))
Log("y", $"unexpected dir: {subdir}");
Directory.Delete(subdir);
count++;
}
return count;
}
int RemoveUnneededFiles(IOPath loc_dir)
{
int count = 0;
foreach (var file in Directory.GetFiles(loc_dir))
{
if(file.EndsWith("l_russian") || file.EndsWith("l_enghish"))
continue;
if (!file.Contains("_l_") || !file.EndsWith(".yml"))
Log("y",$"unexpected file: {file}");
File.Delete(file);
count++;
}
return count;
}
}
} }

View File

@ -56,18 +56,22 @@ static class Merge
File.AppendAllText(out_modlist_file, $"{moddir.LastName()}\n"); File.AppendAllText(out_modlist_file, $"{moddir.LastName()}\n");
} }
public static void ConsoleAskYN(string question, Action yes, Action no) public static void ConsoleAskYN(string question, Action? yes, Action? no)
{ {
Log("y", question + " [y/n]"); Log("y", question + " [y/n]");
string answ = ColoredConsole.Read("w"); string answ = ColoredConsole.Read("w").ToLower();
if (answ == "y") yes(); if (answ == "y") yes?.Invoke();
else no(); else no?.Invoke();
} }
static void HandleConflicts(IOPath[] moddirs) static void HandleConflicts(IOPath[] moddirs)
{ {
var conflicts = Diff.FindModConflicts(moddirs); var conflicts = Diff.FindModConflicts(moddirs);
if (conflicts.Count <= 0) return; conflicts = conflicts.Where(cfl =>
!cfl.FilePath.EndsWith("descriptor.mod") &&
!cfl.FilePath.EndsWith(modlist_filename)).ToList();
if (conflicts.Count < 1)
return;
Diff.LogModConflicts(conflicts); Diff.LogModConflicts(conflicts);
ConsoleAskYN("continue merge?", ConsoleAskYN("continue merge?",
@ -80,4 +84,117 @@ static class Merge
() => {}); () => {});
}); });
} }
public static void UpdateMods(IOPath updated_mods_dir, IOPath[] outdated_dirs, IOPath backup_dir)
{
var src_dir_mods = Directory.GetDirectories(updated_mods_dir).ToList();
List<IOPath> not_found_mods = new List<IOPath>();
List<IOPath> changed_mods = new List<IOPath>();
List<IOPath> unchanged_mods = new List<IOPath>();
foreach (IOPath outdated_mods_dir in outdated_dirs)
foreach (var mod in Directory.GetDirectories(outdated_mods_dir))
{
Log("b", "updating mod ", "c", mod.LastName().Str);
var mod_updated = mod.ReplaceBase(outdated_mods_dir, updated_mods_dir);
int updated_index = src_dir_mods.IndexOf(mod_updated); // IOPath comparison doesnt work?
if (updated_index == -1)
{
Log("m", $"mod {mod.LastName()} not found in {updated_mods_dir}");
not_found_mods.Add(mod);
continue;
}
var diff = Diff.DiffDirs(mod, mod_updated)
.Where(d => d.State != DiffState.Equal)
.ToList();
if (!diff.Any())
{
Log("gray","unchanged");
unchanged_mods.Add(mod);
continue;
}
Log("file difference:");
foreach (var fileD in diff)
{
string color = fileD.State switch
{
DiffState.Added => "g",
DiffState.Changed => "y",
DiffState.Removed => "m",
_ => throw new ArgumentOutOfRangeException()
};
Log(color, fileD.Value.Str);
}
ConsoleAskYN("replace mod with its updated version?",
() =>
{
var mod_backup = Path.Concat(backup_dir, mod.LastName());
Log($"moving {mod} to {backup_dir}");
Directory.Move(mod, mod_backup, false);
Log($"copying {mod_updated} to {outdated_mods_dir}");
Directory.Copy(mod_updated, mod, false);
Log("g", $"mod {mod.LastName()} updated");
changed_mods.Add(mod);
},
()=>unchanged_mods.Add(mod));
}
List<IOPath> added_mods = new List<IOPath>(src_dir_mods.Count - changed_mods.Count);
var found_mods = Enumerable
.Concat(changed_mods, unchanged_mods)
.Select(m=>Path.Concat(updated_mods_dir, m.LastName()))
.ToList();
foreach (var modD in Diff.DiffCollections(found_mods, src_dir_mods))
{
if (modD.State == DiffState.Added)
added_mods.Add(modD.Value);
}
Log("b", "mod update summary:");
if (added_mods.Count > 0)
Log("w", $"added {added_mods.Count}:\n",
"g", added_mods.MergeToString('\n'));
if (changed_mods.Count > 0)
Log("w", $"changed {changed_mods.Count}:\n",
"y", changed_mods.MergeToString('\n'));
if (not_found_mods.Count > 0)
Log("w", $"not found {not_found_mods.Count}:\n",
"m", not_found_mods.MergeToString('\n'));
if (unchanged_mods.Count>0)
Log("w", $"unchanged {unchanged_mods.Count}");
}
public static void RenameModsCommandHandler(string dir_with_mods, string rename_pairs_str)
{
string[] split = Program.SplitArgToStrings(rename_pairs_str, false);
int rename_pairs_length = split.Length / 2;
if (split.Length % 2 != 0)
throw new Exception($"rename_pairs length is not even ({rename_pairs_length})");
var rename_pairs = new (string old_name, string new_name)[rename_pairs_length];
for (int i = 0; i < rename_pairs_length; i++)
{
rename_pairs[i] = (split[i * 2], split[i * 2 + 1]);
}
RenameMods(dir_with_mods, rename_pairs);
}
public static void RenameMods(IOPath dir_with_mods, (string old_name,string new_name)[] rename_pairs)
{
foreach (var re in rename_pairs)
{
var old_mod_path = Path.Concat(dir_with_mods, re.old_name);
var new_mod_path = Path.Concat(dir_with_mods, re.new_name);
if (Directory.Exists(old_mod_path))
{
Log("b","renaming ", "c", re.old_name, "b", " to ", "c", re.new_name);
Directory.Move(old_mod_path, new_mod_path, false);
}
}
}
} }

View File

@ -6,6 +6,8 @@ global using DTLib;
global using DTLib.Extensions; global using DTLib.Extensions;
global using DTLib.Filesystem; global using DTLib.Filesystem;
global using DTLib.Logging; global using DTLib.Logging;
using System.Linq;
using System.Security.Cryptography;
using DTLib.Console; using DTLib.Console;
namespace ParadoxModMerger; namespace ParadoxModMerger;
@ -15,7 +17,7 @@ public static class Program
static ConsoleLogger logger = new($"logs", "main"); static ConsoleLogger logger = new($"logs", "main");
static void Log(params string[] msg) => logger.Log(msg); static void Log(params string[] msg) => logger.Log(msg);
static void Main(string[] args) static int Main(string[] args)
{ {
try try
{ {
@ -25,47 +27,62 @@ public static class Program
string outPath = "" ; string outPath = "" ;
new LaunchArgumentParser( new LaunchArgumentParser(
new LaunchArgument(new []{"o", "out"}, new LaunchArgument(new []{"o", "out"},
"sets output path", "sets output path",
p => outPath=p, p => outPath=p,
"out_path", "out_path",
0), 0),
new LaunchArgument(new []{"clear"}, new LaunchArgument(new []{"clear"},
"Clear mod files and put them into separate dirs in output dir. Requires -o", "Clear mod files and put them into separate dirs in output dir. Requires -o",
wdir=>Workshop.ClearWorkshop(wdir, outPath), wdir=>Workshop.ClearWorkshop(wdir, outPath),
"workshop_dir", "workshop_dir",
1), 1),
new LaunchArgument(new []{"diff"}, new LaunchArgument(new []{"diff"},
"Compares mod files by hash", "Compares mod files by hash",
p=>Diff.DiffCommandHandler(p), p=>Diff.DiffCommandHandler(p),
"first_mod_directory:second_mod_directory:...", "first_mod_directory:second_mod_directory:...",
1), 1),
new LaunchArgument(new []{"diff-detailed"}, new LaunchArgument(new []{"diff-detailed"},
"reads conflicts_XXX.dtsod file and shows text diff for each file", "reads conflicts_XXX.dtsod file and shows text diff for each file",
p=>Diff.DiffDetailedCommandHandler(p), p=>Diff.DiffDetailedCommandHandler(p),
"conflicts_dtsod_path", "conflicts_dtsod_path",
1 1),
), new LaunchArgument(new []{"merge-subdirs"},
new LaunchArgument(new []{"merge-subdirs"}, "Merges mods and shows conflicts. Requires -o",
"Merges mods and shows conflicts. Requires -o", d => Merge.MergeAll(Directory.GetDirectories(d), outPath),
d => Merge.MergeAll(Directory.GetDirectories(d), outPath), "dir_with_mods",
"dir_with_mods", 1),
1), new LaunchArgument(new []{"merge-into", "merge-single"},
new LaunchArgument(new []{"merge-into", "merge-single"}, "Merges one mod into output dir and shows conflicts. Requires -o",
"Merges one mod into output dir and shows conflicts. Requires -o", mod=>Merge.MergeInto(mod, outPath),
mod=>Merge.MergeInto(mod, outPath), "mod_dir",
"mod_dir", 1),
1), new LaunchArgument(new []{"gen-rus-locale"},
new LaunchArgument(new []{"gen-rus-locale"}, "Creates l_russian copy of english locale in output directory. Requires -o",
"Creates l_russian copy of english locale in output directory. Requires -o", eng=>Localisation.GenerateRussian(eng, outPath),
eng=>Localisation.GenerateRussian(eng, outPath), "english_locale_path",
"english_locale_path", 1),
1), new LaunchArgument(new []{"desc"},
new LaunchArgument(new []{"desc"}, "Downloads mod description from steam to new file in outDir. Requires -o",
"Downloads mod description from steam to new file in outDir. Requires -o", id=>Workshop.CreateDescFile(id, outPath).GetAwaiter().GetResult(),
id=>Workshop.CreateDescFile(id, outPath).GetAwaiter().GetResult(), "mod_id",
"mod_id", 1),
1) new LaunchArgument(new []{"rename"},
"Renames mods in directory",
(modsdir, replace_pairs)=>Merge.RenameModsCommandHandler(modsdir, replace_pairs),
"dir_with_mods", "replace_pairs (old_name:new_name:...)",
1),
new LaunchArgument(new []{"update-mods"},
"Updates mods in [outdated_dir0...outdated_dirN] to new versions if found in updated_mods_dir. " +
"Moves old mods to backup_dir defined by -o.",
(updated, outdated)=>Merge.UpdateMods(updated, SplitArgToPaths(outdated, true), outPath),
"updated_mods_dir", "outdated_dir OR outdated_dir0:...:outdated_dirN",
1),
new LaunchArgument(new []{"clean-locales"},
"Deletes all localisations except l_russian and l_english.",
locdir=> Localisation.Clean(locdir),
"localisation_dir",
1)
).ParseAndHandle(args); ).ParseAndHandle(args);
} }
catch (LaunchArgumentParser.ExitAfterHelpException) catch (LaunchArgumentParser.ExitAfterHelpException)
@ -73,23 +90,27 @@ public static class Program
catch (Exception ex) catch (Exception ex)
{ {
Log("r", DTLib.Ben.Demystifier.ExceptionExtensions.ToStringDemystified(ex)); Log("r", DTLib.Ben.Demystifier.ExceptionExtensions.ToStringDemystified(ex));
Console.ResetColor();
return 1;
} }
Console.ResetColor();
return 0;
} }
public static IOPath[] SplitStringToPaths(string connected_paths) public static string[] SplitArgToStrings(string connected_parts, bool allow_one_part)
{ {
char part_sep; char part_sep;
if (connected_paths.Contains(':')) if (connected_parts.Contains(':'))
part_sep = ':'; part_sep = ':';
else if (!connected_paths.Contains(':')) else if (connected_parts.Contains(';'))
part_sep = ';'; part_sep = ';';
else throw new Exception($"<{connected_paths}> doesn't contain any separators (:/;)"); else if (allow_one_part)
return new []{connected_parts};
else throw new Exception($"<{connected_parts}> doesn't contain any separators (:/;)");
string[] split = connected_paths.Split(part_sep); return connected_parts.Split(part_sep);
IOPath[] split_iop = new IOPath[split.Length];
for (int i = 0; i < split.Length; i++)
split_iop[i] = new IOPath(split[i]);
return split_iop;
} }
public static IOPath[] SplitArgToPaths(string connected_parts, bool allow_one_part) =>
IOPath.ArrayCast(SplitArgToStrings(connected_parts, allow_one_part));
} }